/* * 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.util; import com.android.SdkConstants; import com.android.jarutils.SignedJarBuilder; import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.sdklib.BuildToolInfo; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.internal.project.ProjectProperties; import com.google.common.base.Charsets; import com.google.common.io.Files; import com.intellij.execution.process.BaseOSProcessHandler; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; 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.execution.ParametersListUtil; import org.jetbrains.android.AndroidCommonBundle; import org.jetbrains.android.sdk.MessageBuildingSdkLog; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.*; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.regex.Pattern; /** * @author Eugene.Kudelevsky */ public class AndroidCommonUtils { @NonNls public static final String PROGUARD_CFG_FILE_NAME = "proguard-project.txt"; public static final String SDK_HOME_MACRO = "%MODULE_SDK_HOME%"; public static final String PROGUARD_SYSTEM_CFG_FILE_URL = "file://" + SDK_HOME_MACRO + "/tools/proguard/proguard-android.txt"; private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.util.AndroidCommonUtils"); @NonNls public static final String MANIFEST_JAVA_FILE_NAME = "Manifest.java"; @NonNls public static final String R_JAVA_FILENAME = "R.java"; @NonNls public static final String CLASSES_JAR_FILE_NAME = "classes.jar"; @NonNls public static final String AAR_DEPS_JAR_FILE_NAME = "aar_deps.jar"; @NonNls public static final String CLASSES_FILE_NAME = "classes.dex"; private static final Pattern WARNING_PATTERN = Pattern.compile(".*warning.*"); private static final Pattern ERROR_PATTERN = Pattern.compile(".*error.*"); private static final Pattern EXCEPTION_PATTERN = Pattern.compile(".*exception.*"); public static final Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); private static final Pattern MANIFEST_PATTERN = Pattern.compile("Manifest(\\$.*)?\\.class"); private static final String BUILD_CONFIG_CLASS_NAME = "BuildConfig.class"; public static final Pattern COMPILER_MESSAGE_PATTERN = Pattern.compile("(.+):(\\d+):.+"); @NonNls public static final String PNG_EXTENSION = "png"; private static final String[] DRAWABLE_EXTENSIONS = new String[]{PNG_EXTENSION, "jpg", "gif"}; @NonNls public static final String RELEASE_BUILD_OPTION = "RELEASE_BUILD_KEY"; @NonNls public static final String PROGUARD_CFG_PATHS_OPTION = "ANDROID_PROGUARD_CFG_PATHS"; @NonNls public static final String DIRECTORY_FOR_LOGS_NAME = "proguard_logs"; @NonNls public static final String PROGUARD_OUTPUT_JAR_NAME = "obfuscated_sources.jar"; @NonNls public static final String SYSTEM_PROGUARD_CFG_FILE_NAME = "proguard-android.txt"; @NonNls private static final String PROGUARD_HOME_ENV_VARIABLE = "PROGUARD_HOME"; public static final ResourceType[] ID_PROVIDING_RESOURCE_TYPES = new ResourceType[] { ResourceType.LAYOUT, ResourceType.MENU, ResourceType.DRAWABLE, ResourceType.XML, ResourceType.TRANSITION }; @NonNls public static final String INCLUDE_ASSETS_FROM_LIBRARIES_ELEMENT_NAME = "includeAssetsFromLibraries"; @NonNls public static final String ADDITIONAL_NATIVE_LIBS_ELEMENT = "additionalNativeLibs"; @NonNls public static final String ITEM_ELEMENT = "item"; @NonNls public static final String ARCHITECTURE_ATTRIBUTE = "architecture"; @NonNls public static final String URL_ATTRIBUTE = "url"; @NonNls public static final String TARGET_FILE_NAME_ATTRIBUTE = "targetFileName"; private static final String[] TEST_CONFIGURATION_TYPE_IDS = {"JUnit", "TestNG", "ScalaTestRunConfiguration", "SpecsRunConfiguration", "Specs2RunConfiguration"}; @NonNls public static final String ANNOTATIONS_JAR_RELATIVE_PATH = "/tools/support/annotations.jar"; @NonNls public static final String PACKAGE_MANIFEST_ATTRIBUTE = "package"; @NonNls public static final String ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX = ".afp"; @NonNls public static final String PROGUARD_CFG_OUTPUT_FILE_NAME = "proguard.txt"; @NonNls public static final String MANIFEST_MERGING_BUILD_TARGET_TYPE_ID = "android-manifest-merging"; @NonNls public static final String AAR_DEPS_BUILD_TARGET_TYPE_ID = "android-aar-deps"; @NonNls public static final String DEX_BUILD_TARGET_TYPE_ID = "android-dex"; @NonNls public static final String PRE_DEX_BUILD_TARGET_TYPE_ID = "android-pre-dex"; @NonNls public static final String PACKAGING_BUILD_TARGET_TYPE_ID = "android-packaging"; @NonNls public static final String RESOURCE_CACHING_BUILD_TARGET_ID = "android-resource-caching"; @NonNls public static final String RESOURCE_PACKAGING_BUILD_TARGET_ID = "android-resource-packaging"; @NonNls public static final String LIBRARY_PACKAGING_BUILD_TARGET_ID = "android-library-packaging"; @NonNls public static final String AUTOGENERATED_JAVA_FILE_HEADER = "/*___Generated_by_IDEA___*/"; /** Android Test Run Configuration Type Id, defined here so as to be accessible to both JPS and Android plugin. */ @NonNls public static final String ANDROID_TEST_RUN_CONFIGURATION_TYPE = "AndroidTestRunConfigurationType"; private AndroidCommonUtils() { } public static boolean isTestConfiguration(@NotNull String typeId) { return ArrayUtil.find(TEST_CONFIGURATION_TYPE_IDS, typeId) >= 0; } public static boolean isInstrumentationTestConfiguration(@NotNull String typeId) { return ANDROID_TEST_RUN_CONFIGURATION_TYPE.equals(typeId); } public static String command2string(@NotNull Collection<String> command) { final StringBuilder builder = new StringBuilder(); for (Iterator<String> it = command.iterator(); it.hasNext(); ) { String s = it.next(); builder.append('['); builder.append(s); builder.append(']'); if (it.hasNext()) { builder.append(' '); } } return builder.toString(); } public static void moveAllFiles(@NotNull File from, @NotNull File to, @NotNull Collection<File> newFiles) throws IOException { if (from.isFile()) { FileUtil.rename(from, to); newFiles.add(to); } else { final File[] children = from.listFiles(); if (children != null) { for (File child : children) { moveAllFiles(child, new File(to, child.getName()), newFiles); } } } } public static void handleDexCompilationResult(@NotNull Process process, @NotNull String outputFilePath, @NotNull final Map<AndroidCompilerMessageKind, List<String>> messages) { final BaseOSProcessHandler handler = new BaseOSProcessHandler(process, null, null); handler.addProcessListener(new ProcessAdapter() { private AndroidCompilerMessageKind myCategory = null; @Override public void onTextAvailable(ProcessEvent event, Key outputType) { String[] msgs = event.getText().split("\\n"); for (String msg : msgs) { msg = msg.trim(); String msglc = msg.toLowerCase(); if (outputType == ProcessOutputTypes.STDERR) { if (WARNING_PATTERN.matcher(msglc).matches()) { myCategory = AndroidCompilerMessageKind.WARNING; } if (ERROR_PATTERN.matcher(msglc).matches() || EXCEPTION_PATTERN.matcher(msglc).matches() || myCategory == null) { myCategory = AndroidCompilerMessageKind.ERROR; } messages.get(myCategory).add(msg); } else if (outputType == ProcessOutputTypes.STDOUT) { if (!msglc.startsWith("processing")) { messages.get(AndroidCompilerMessageKind.INFORMATION).add(msg); } } LOG.debug(msg); } } }); handler.startNotify(); handler.waitFor(); final List<String> errors = messages.get(AndroidCompilerMessageKind.ERROR); if (new File(outputFilePath).isFile()) { // if compilation finished correctly, show all errors as warnings messages.get(AndroidCompilerMessageKind.WARNING).addAll(errors); errors.clear(); } else if (errors.size() == 0) { errors.add("Cannot create classes.dex file"); } } @NotNull public static List<String> packClassFilesIntoJar(@NotNull String[] firstPackageDirPaths, @NotNull String[] libFirstPackageDirPaths, @NotNull File jarFile) throws IOException { final List<Pair<File, String>> files = new ArrayList<Pair<File, String>>(); for (String path : firstPackageDirPaths) { final File firstPackageDir = new File(path); if (firstPackageDir.exists()) { packClassFilesIntoJar(firstPackageDir, firstPackageDir.getParentFile(), true, files); } } for (String path : libFirstPackageDirPaths) { final File firstPackageDir = new File(path); if (firstPackageDir.exists()) { packClassFilesIntoJar(firstPackageDir, firstPackageDir.getParentFile(), false, files); } } if (files.size() > 0) { final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile)); try { for (Pair<File, String> pair : files) { packIntoJar(jos, pair.getFirst(), pair.getSecond()); } } finally { jos.close(); } } else if (jarFile.isFile()) { if (!jarFile.delete()) { throw new IOException("Cannot delete file " + FileUtil.toSystemDependentName(jarFile.getPath())); } } final List<String> srcFiles = new ArrayList<String>(); for (Pair<File, String> pair : files) { srcFiles.add(pair.getFirst().getPath()); } return srcFiles; } private static void packClassFilesIntoJar(@NotNull File file, @NotNull File rootDirectory, boolean packRAndManifestClasses, @NotNull List<Pair<File, String>> files) throws IOException { if (file.isDirectory()) { final File[] children = file.listFiles(); if (children != null) { for (File child : children) { packClassFilesIntoJar(child, rootDirectory, packRAndManifestClasses, files); } } } else if (file.isFile()) { if (!FileUtilRt.extensionEquals(file.getName(), "class")) { return; } if (!packRAndManifestClasses && (R_PATTERN.matcher(file.getName()).matches() || MANIFEST_PATTERN.matcher(file.getName()).matches() || BUILD_CONFIG_CLASS_NAME.equals(file.getName()))) { return; } final String rootPath = rootDirectory.getAbsolutePath(); String path = file.getAbsolutePath(); path = FileUtil.toSystemIndependentName(path.substring(rootPath.length())); if (path.charAt(0) == '/') { path = path.substring(1); } files.add(Pair.create(file, path)); } } public static void packIntoJar(@NotNull JarOutputStream jar, @NotNull File file, @NotNull String path) throws IOException { final JarEntry entry = new JarEntry(path); entry.setTime(file.lastModified()); jar.putNextEntry(entry); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); try { final byte[] buffer = new byte[1024]; int count; while ((count = bis.read(buffer)) != -1) { jar.write(buffer, 0, count); } jar.closeEntry(); } finally { bis.close(); } } @Nullable public static String getResourceTypeByDirName(@NotNull String name) { ResourceFolderType folderType = ResourceFolderType.getFolderType(name); return folderType != null ? folderType.getName() : null; } @NotNull public static String getResourceName(@NotNull String resourceType, @NotNull String fileName) { final String s = FileUtil.getNameWithoutExtension(fileName); return resourceType.equals("drawable") && s.endsWith(".9") && FileUtilRt.extensionEquals(fileName, PNG_EXTENSION) ? s.substring(0, s.length() - 2) : s; } @NotNull public static String getResourceTypeByTagName(@NotNull String tagName) { if (tagName.equals("declare-styleable")) { tagName = "styleable"; } else if (tagName.endsWith("-array")) { tagName = "array"; } return tagName; } @NotNull public static Map<AndroidCompilerMessageKind, List<String>> launchProguard(@NotNull IAndroidTarget target, int sdkToolsRevision, @NotNull String sdkOsPath, @NotNull String javaExecutablePath, @NotNull String proguardVmOptions, @NotNull String[] proguardConfigFileOsPaths, @NotNull String inputJarOsPath, @NotNull String[] externalJarOsPaths, @NotNull String[] providedJarOsPaths, @NotNull String outputJarFileOsPath, @Nullable String logDirOutputOsPath) throws IOException { final List<String> commands = new ArrayList<String>(); commands.add(javaExecutablePath); if (proguardVmOptions.length() > 0) { commands.addAll(ParametersListUtil.parse(proguardVmOptions)); } commands.add("-jar"); final String proguardHome = getProguardHomeDirOsPath(sdkOsPath); final String proguardJarOsPath = proguardHome + File.separator + "lib" + File.separator + "proguard.jar"; commands.add(proguardJarOsPath); if (isIncludingInProguardSupported(sdkToolsRevision)) { for (String proguardConfigFileOsPath : proguardConfigFileOsPaths) { commands.add("-include"); commands.add(quotePath(proguardConfigFileOsPath)); } } else { commands.add("@" + quotePath(proguardConfigFileOsPaths[0])); } commands.add("-injars"); StringBuilder builder = new StringBuilder(quotePath(inputJarOsPath)); for (String jarFile : externalJarOsPaths) { builder.append(File.pathSeparatorChar); builder.append(quotePath(jarFile)); } commands.add(builder.toString()); commands.add("-outjars"); commands.add(quotePath(outputJarFileOsPath)); commands.add("-libraryjars"); builder = new StringBuilder(quotePath(target.getPath(IAndroidTarget.ANDROID_JAR))); IAndroidTarget.IOptionalLibrary[] libraries = target.getOptionalLibraries(); if (libraries != null) { for (IAndroidTarget.IOptionalLibrary lib : libraries) { builder.append(File.pathSeparatorChar); builder.append(quotePath(lib.getJarPath())); } } for (String path : providedJarOsPaths) { builder.append(File.pathSeparatorChar); builder.append(quotePath(path)); } commands.add(builder.toString()); if (logDirOutputOsPath != null) { commands.add("-dump"); commands.add(quotePath(new File(logDirOutputOsPath, "dump.txt").getAbsolutePath())); commands.add("-printseeds"); commands.add(quotePath(new File(logDirOutputOsPath, "seeds.txt").getAbsolutePath())); commands.add("-printusage"); commands.add(quotePath(new File(logDirOutputOsPath, "usage.txt").getAbsolutePath())); commands.add("-printmapping"); commands.add(quotePath(new File(logDirOutputOsPath, "mapping.txt").getAbsolutePath())); } LOG.info(command2string(commands)); final Map<String, String> home = System.getenv().containsKey(PROGUARD_HOME_ENV_VARIABLE) ? Collections.<String, String>emptyMap() : Collections.singletonMap(PROGUARD_HOME_ENV_VARIABLE, proguardHome); return AndroidExecutionUtil.doExecute(ArrayUtil.toStringArray(commands), home); } @NotNull public static String getProguardHomeDirOsPath(@NotNull String sdkOsPath) { return sdkOsPath + File.separator + SdkConstants.FD_TOOLS + File.separator + SdkConstants.FD_PROGUARD; } @NotNull public static String getProguardHomeDirPath(@NotNull String sdkOsPath) { return FileUtil.toSystemIndependentName(getProguardHomeDirOsPath(sdkOsPath)); } public static String getProguardSystemCfgPath(@NotNull String sdkOsPath) { return getProguardHomeDirPath(sdkOsPath) + '/' + SYSTEM_PROGUARD_CFG_FILE_NAME; } private static String quotePath(String path) { if (path.indexOf(' ') != -1) { path = '\'' + path + '\''; } return path; } public static String buildTempInputJar(@NotNull String[] classFilesDirOsPaths, @NotNull String[] libClassFilesDirOsPaths) throws IOException { final File inputJar = FileUtil.createTempFile("proguard_input", ".jar"); packClassFilesIntoJar(classFilesDirOsPaths, libClassFilesDirOsPaths, inputJar); return FileUtil.toSystemDependentName(inputJar.getPath()); } public static String toolPath(@NotNull String toolFileName) { return SdkConstants.OS_SDK_TOOLS_FOLDER + toolFileName; } public static String platformToolPath(@NotNull String toolFileName) { return SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + toolFileName; } public static boolean isIncludingInProguardSupported(int sdkToolsRevision) { return sdkToolsRevision == -1 || sdkToolsRevision >= 17; } public static int parsePackageRevision(@NotNull String sdkDirOsPath, @NotNull String packageDirName) { final File propFile = new File(sdkDirOsPath + File.separatorChar + packageDirName + File.separatorChar + SdkConstants.FN_SOURCE_PROP); int revisionNumber = -1; if (propFile.exists() && propFile.isFile()) { final Map<String, String> map = ProjectProperties.parsePropertyFile(new BufferingFileWrapper(propFile), new MessageBuildingSdkLog()); if (map == null) { return -1; } String revision = map.get("Pkg.Revision"); if (revision != null) { final int dot = revision.indexOf('.'); if (dot > 0) { revision = revision.substring(0, dot); } try { revisionNumber = Integer.parseInt(revision); } catch (NumberFormatException e) { LOG.debug(e); } } } return revisionNumber > 0 ? revisionNumber : -1; } @NotNull public static String readFile(@NotNull File file) throws IOException { return Files.toString(file, Charsets.UTF_8); } public static boolean contains2Identifiers(String packageName) { return packageName.split("\\.").length >= 2; } public static boolean directoriesContainSameContent(@NotNull File dir1, @NotNull File dir2, @Nullable FileFilter filter) throws IOException { if (dir1.exists() != dir2.exists()) { return false; } final File[] children1 = getFilteredChildren(dir1, filter); final File[] children2 = getFilteredChildren(dir2, filter); if (children1 == null || children2 == null) { return children1 == children2; } if (children1.length != children2.length) { return false; } for (int i = 0; i < children1.length; i++) { final File child1 = children1[i]; final File child2 = children2[i]; if (!Comparing.equal(child1.getName(), child2.getName())) { return false; } final boolean childDir = child1.isDirectory(); if (childDir != child2.isDirectory()) { return false; } if (childDir) { if (!directoriesContainSameContent(child1, child2, filter)) { return false; } } else { final String content1 = readFile(child1); final String content2 = readFile(child2); if (!Comparing.equal(content1, content2)) { return false; } } } return true; } @Nullable private static File[] getFilteredChildren(@NotNull File dir, @Nullable FileFilter filter) { final File[] children = dir.listFiles(); if (children == null || children.length == 0 || filter == null) { return children; } final List<File> result = new ArrayList<File>(); for (File child : children) { if (child.isDirectory() || filter.accept(child)) { result.add(child); } } return result.toArray(new File[result.size()]); } @NotNull public static String addSuffixToFileName(@NotNull String path, @NotNull String suffix) { final int dot = path.lastIndexOf('.'); if (dot < 0) { return path + suffix; } final String a = path.substring(0, dot); final String b = path.substring(dot); return a + suffix + b; } public static void signApk(@NotNull File srcApk, @NotNull File destFile, @NotNull PrivateKey privateKey, @NotNull X509Certificate certificate) throws IOException, GeneralSecurityException { FileOutputStream fos = new FileOutputStream(destFile); SignedJarBuilder builder = new SafeSignedJarBuilder(fos, privateKey, certificate, destFile.getPath()); FileInputStream fis = new FileInputStream(srcApk); try { builder.writeZip(fis, null); builder.close(); } finally { try { fis.close(); } catch (IOException ignored) { } finally { try { fos.close(); } catch (IOException ignored) { } } } } @NotNull public static String getStackTrace(@NotNull Throwable t) { final StringWriter stringWriter = new StringWriter(); final PrintWriter writer = new PrintWriter(stringWriter); try { t.printStackTrace(writer); return stringWriter.toString(); } finally { writer.close(); } } public static boolean hasXmxParam(@NotNull List<String> parameters) { for (String param : parameters) { if (param.startsWith("-Xmx")) { return true; } } return false; } @Nullable public static String executeZipAlign(@NotNull String zipAlignPath, @NotNull File source, @NotNull File destination) { final ProcessBuilder processBuilder = new ProcessBuilder( zipAlignPath, "-f", "4", source.getAbsolutePath(), destination.getAbsolutePath()); BaseOSProcessHandler handler; try { handler = new BaseOSProcessHandler(processBuilder.start(), "", null); } catch (IOException e) { return e.getMessage(); } final StringBuilder builder = new StringBuilder(); handler.addProcessListener(new ProcessAdapter() { @Override public void onTextAvailable(ProcessEvent event, Key outputType) { builder.append(event.getText()); } }); handler.startNotify(); handler.waitFor(); int exitCode = handler.getProcess().exitValue(); return exitCode != 0 ? builder.toString() : null; } @NotNull public static Map<AndroidCompilerMessageKind, List<String>> buildArtifact(@NotNull String artifactName, @NotNull String messagePrefix, @NotNull String sdkLocation, @NotNull IAndroidTarget target, @Nullable String artifactFilePath, @NotNull String keyStorePath, @Nullable String keyAlias, @Nullable String keyStorePassword, @Nullable String keyPassword) throws GeneralSecurityException, IOException { final Map<AndroidCompilerMessageKind, List<String>> messages = new HashMap<AndroidCompilerMessageKind, List<String>>(); messages.put(AndroidCompilerMessageKind.ERROR, new ArrayList<String>()); messages.put(AndroidCompilerMessageKind.WARNING, new ArrayList<String>()); messages.put(AndroidCompilerMessageKind.INFORMATION, new ArrayList<String>()); final Pair<PrivateKey, X509Certificate> pair = getPrivateKeyAndCertificate(messagePrefix, messages, keyAlias, keyStorePath, keyStorePassword, keyPassword); if (pair == null) { return messages; } final String prefix = "Cannot sign artifact " + artifactName + ": "; if (artifactFilePath == null) { messages.get(AndroidCompilerMessageKind.ERROR).add(prefix + "output path is not specified"); return messages; } final File artifactFile = new File(artifactFilePath); if (!artifactFile.exists()) { messages.get(AndroidCompilerMessageKind.ERROR).add(prefix + "file " + artifactFilePath + " hasn't been generated"); return messages; } final String zipAlignPath = getZipAlign(sdkLocation, target); final boolean runZipAlign = new File(zipAlignPath).isFile(); File tmpDir = null; try { tmpDir = FileUtil.createTempDirectory("android_artifact", "tmp"); final File tmpArtifact = new File(tmpDir, "tmpArtifact.apk"); signApk(artifactFile, tmpArtifact, pair.getFirst(), pair.getSecond()); if (!FileUtil.delete(artifactFile)) { messages.get(AndroidCompilerMessageKind.ERROR).add("Cannot delete file " + artifactFile.getPath()); return messages; } if (runZipAlign) { final String errorMessage = executeZipAlign(zipAlignPath, tmpArtifact, artifactFile); if (errorMessage != null) { messages.get(AndroidCompilerMessageKind.ERROR).add(messagePrefix + "zip-align: " + errorMessage); return messages; } } else { messages.get(AndroidCompilerMessageKind.WARNING).add(messagePrefix + AndroidCommonBundle.message( "android.artifact.building.cannot.find.zip.align.error")); FileUtil.copy(tmpArtifact, artifactFile); } } finally { if (tmpDir != null) { FileUtil.delete(tmpDir); } } return messages; } @Nullable private static Pair<PrivateKey, X509Certificate> getPrivateKeyAndCertificate(@NotNull String errorPrefix, @NotNull Map<AndroidCompilerMessageKind, List<String>> messages, @Nullable String keyAlias, @Nullable String keyStoreFilePath, @Nullable String keyStorePasswordStr, @Nullable String keyPasswordStr) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableEntryException { if (keyStoreFilePath == null || keyStoreFilePath.length() == 0) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + "Key store file is not specified"); return null; } if (keyStorePasswordStr == null) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + "Key store password is not specified"); return null; } if (keyAlias == null || keyAlias.length() == 0) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + "Key alias is not specified"); return null; } if (keyPasswordStr == null) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + "Key password is not specified"); return null; } final File keyStoreFile = new File(keyStoreFilePath); final char[] keystorePassword = keyStorePasswordStr.toCharArray(); final char[] plainKeyPassword = keyPasswordStr.toCharArray(); final KeyStore keyStore; InputStream is = null; try { //noinspection IOResourceOpenedButNotSafelyClosed is = new FileInputStream(keyStoreFile); keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(is, keystorePassword); final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(plainKeyPassword)); if (entry == null) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + AndroidCommonBundle.message( "android.artifact.building.cannot.find.key.error", keyAlias)); return null; } final PrivateKey privateKey = entry.getPrivateKey(); final Certificate certificate = entry.getCertificate(); if (privateKey == null || certificate == null) { messages.get(AndroidCompilerMessageKind.ERROR).add(errorPrefix + AndroidCommonBundle.message( "android.artifact.building.cannot.find.key.error", keyAlias)); return null; } return Pair.create(privateKey, (X509Certificate)certificate); } finally { if (is != null) { try { is.close(); } catch (IOException e) { LOG.info(e); } } } } @NotNull public static String getZipAlign(@NotNull String sdkPath, @NotNull IAndroidTarget target) { final BuildToolInfo buildToolInfo = target.getBuildToolInfo(); if (buildToolInfo != null) { String path = null; try { path = buildToolInfo.getPath(BuildToolInfo.PathId.ZIP_ALIGN); } catch (Throwable ignored) { } if (path != null && new File(path).exists()) { return path; } } return sdkPath + File.separatorChar + toolPath(SdkConstants.FN_ZIPALIGN); } }