Ensures debug keystores are created.
Xavier Ducrohet [Thu, 17 Jan 2013 00:24:59 +0000 (16:24 -0800)]
For Each SigningConfig object, create a ValidateSigningTask
that creates the keystore if:
- the path match the default debug keystore
- the keystore is missing

Packaging tasks are made to depend on the validate task
matching the SigningConfig they are configured with.

The main reason to do that versus doing the creation
inside the packaging app is that the way gradle
handles input file, the file has to exist.
The optional annotation let the input be null,
but not be a value that doesn't match an existing
file.

Also adds the store type in the SigningConfig as it's necessary for
both creating and loaded a keystore.

Finally, this fixes the API of the packaging a bit:
- pass the SigningConfig directly everywhere.
- have the Builder throw the actual exceptions instead of
  RuntimeException (so that the caller can decide what to do)
- Make SigningConfigDsl extend the getters to be able to
  annotate them with @Input
- Clean up the DebugKeyHelper vs. KeystoreHelper.

Change-Id: If5eda8e2c2e759ace393d0689fa76699e3a9b6ba

30 files changed:
builder/src/main/java/com/android/builder/AndroidBuilder.java
builder/src/main/java/com/android/builder/BuildType.java
builder/src/main/java/com/android/builder/ProductFlavor.java
builder/src/main/java/com/android/builder/VariantConfiguration.java
builder/src/main/java/com/android/builder/internal/packaging/Packager.java
builder/src/main/java/com/android/builder/internal/signing/DebugKeyHelper.java [deleted file]
builder/src/main/java/com/android/builder/internal/signing/DebugKeyProvider.java [deleted file]
builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
builder/src/main/java/com/android/builder/packaging/SigningException.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/signing/CertificateInfo.java [moved from builder/src/main/java/com/android/builder/internal/signing/CertificateInfo.java with 96% similarity]
builder/src/main/java/com/android/builder/signing/KeystoreHelper.java [moved from builder/src/main/java/com/android/builder/internal/signing/KeystoreHelper.java with 60% similarity]
builder/src/main/java/com/android/builder/signing/KeytoolException.java [moved from builder/src/main/java/com/android/builder/internal/signing/KeytoolException.java with 96% similarity]
builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java [moved from builder/src/main/java/com/android/builder/internal/signing/SignedJarBuilder.java with 99% similarity]
builder/src/main/java/com/android/builder/signing/SigningConfig.java [moved from builder/src/main/java/com/android/builder/SigningConfig.java with 82% similarity]
builder/src/test/java/com/android/builder/internal/incremental/FileManagerTest.java
builder/src/test/java/com/android/builder/signing/KeyStoreHelperTest.java [moved from builder/src/test/java/com/android/builder/internal/signing/DebugKeyProviderTest.java with 71% similarity]
gradle/src/main/groovy/com/android/build/gradle/AppExtension.groovy
gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/BuildVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.groovy
gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/DefaultBuildVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeDsl.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigDsl.java
gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PackageApplicationTask.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy [new file with mode: 0644]
gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy

index 0c9e76f..3aa9cc4 100644 (file)
@@ -28,16 +28,16 @@ import com.android.builder.internal.compiler.AidlProcessor;
 import com.android.builder.internal.compiler.SourceGenerator;
 import com.android.builder.internal.packaging.JavaResourceProcessor;
 import com.android.builder.internal.packaging.Packager;
-import com.android.builder.internal.signing.DebugKeyHelper;
-import com.android.builder.internal.signing.KeystoreHelper;
-import com.android.builder.internal.signing.KeytoolException;
-import com.android.builder.internal.signing.CertificateInfo;
 import com.android.builder.packaging.DuplicateFileException;
 import com.android.builder.packaging.PackagerException;
 import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.packaging.SigningException;
+import com.android.builder.signing.CertificateInfo;
+import com.android.builder.signing.KeystoreHelper;
+import com.android.builder.signing.KeytoolException;
+import com.android.builder.signing.SigningConfig;
 import com.android.manifmerger.ManifestMerger;
 import com.android.manifmerger.MergerLog;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
 import com.android.sdklib.internal.repository.packages.FullRevision;
@@ -73,7 +73,7 @@ import static com.google.common.base.Preconditions.checkState;
  * {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.VariantConfiguration.Type, boolean, AaptOptions)}
  * {@link #compileAidl(java.util.List, java.io.File, java.util.List)}
  * {@link #convertByteCode(Iterable, Iterable, String, DexOptions, boolean)}
- * {@link #packageApk(String, String, java.util.List, String, String, boolean, java.io.File, String, String, String, String)}
+ * {@link #packageApk(String, String, java.util.List, String, String, boolean, com.android.builder.signing.SigningConfig, String)}
  *
  * Java compilation is not handled but the builder provides the runtime classpath with
  * {@link #getRuntimeClasspath()}.
@@ -747,12 +747,13 @@ public class AndroidBuilder {
      * @param javaResourcesLocation the processed Java resource folder
      * @param jniLibsLocation the location of the compiled JNI libraries
      * @param debugJni whether the app should include jni debug data
-     * @param signingStoreLocation signing store location (if not debug signed)
-     * @param signingStorePassword signing store password (if not debug signed)
-     * @param signingKeyAlias signing key alias (if not debug signed)
-     * @param signingKeyPassword signing key password (if not debug signed)
+     * @param signingConfig the signing configuration
      * @param outApkLocation location of the APK.
      * @throws DuplicateFileException
+     * @throws FileNotFoundException if the store location was not found
+     * @throws KeytoolException
+     * @throws PackagerException
+     * @throws SigningException when the key cannot be read from the keystore
      *
      * @see com.android.builder.VariantConfiguration#getPackagedJars()
      */
@@ -763,39 +764,20 @@ public class AndroidBuilder {
             @Nullable String javaResourcesLocation,
             @Nullable String jniLibsLocation,
             boolean debugJni,
-            @Nullable File signingStoreLocation,
-            @Nullable String signingStorePassword,
-            @Nullable String signingKeyAlias,
-            @Nullable String signingKeyPassword,
-            @NonNull String outApkLocation) throws DuplicateFileException {
+            @Nullable SigningConfig signingConfig,
+            @NonNull String outApkLocation) throws DuplicateFileException, FileNotFoundException,
+            KeytoolException, PackagerException, SigningException {
         checkState(mTarget != null, "Target not set.");
         checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
         checkNotNull(classesDexLocation, "classesDexLocation cannot be null.");
         checkNotNull(outApkLocation, "outApkLocation cannot be null.");
 
         CertificateInfo certificateInfo = null;
-        try {
-            if (signingStoreLocation != null &&
-                    signingStorePassword != null &&
-                    signingKeyAlias != null &&
-                    signingKeyPassword != null) {
-                if (DebugKeyHelper.defaultDebugKeyStoreLocation().equals(signingStoreLocation)) {
-                    createDebugKeystore(signingStoreLocation.getAbsolutePath());
-                }
-                certificateInfo = KeystoreHelper.getSigningInfo(
-                        signingStoreLocation.getAbsolutePath(),
-                        signingStorePassword,
-                        null, /*storeStype*/
-                        signingKeyAlias,
-                        signingKeyPassword);
+        if (signingConfig != null && signingConfig.isSigningReady()) {
+            certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig);
+            if (certificateInfo == null) {
+                throw new SigningException("Failed to read key from keystore");
             }
-        } catch (AndroidLocationException e) {
-            throw new RuntimeException(e);
-        } catch (KeytoolException e) {
-            throw new RuntimeException(e);
-        } catch (FileNotFoundException e) {
-            // this shouldn't happen as we have checked ahead of calling getDebugKey.
-            throw new RuntimeException(e);
         }
 
         try {
@@ -823,30 +805,9 @@ public class AndroidBuilder {
             }
 
             packager.sealApk();
-        } catch (PackagerException e) {
-            throw new RuntimeException(e);
         } catch (SealedPackageException e) {
+            // shouldn't happen since we control the package from start to end.
             throw new RuntimeException(e);
         }
     }
-
-    /**
-     * Creates a debug signing keystore at the given location if one does not already exist.
-     *
-     * @param storeLocation the fully-qualified path where the keystore should live.
-     * @throws KeytoolException
-     */
-    private void createDebugKeystore(String storeLocation) throws KeytoolException {
-        File storeFile = new File(storeLocation);
-        if (storeFile.isDirectory()) {
-            throw new RuntimeException(
-                    String.format("A folder is in the way of the debug keystore: %s",
-                            storeLocation));
-        } else if (!storeFile.exists()) {
-            if (!DebugKeyHelper.createNewStore(
-                    storeLocation, null /*storeType*/, mLogger)) {
-                throw new RuntimeException("Unable to recreate missing debug keystore.");
-            }
-        }
-    }
 }
index 800064f..55a137c 100644 (file)
@@ -18,6 +18,7 @@ package com.android.builder;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.builder.signing.SigningConfig;
 import com.google.common.base.Objects;
 
 public class BuildType extends BuildConfig {
index c0bc5c6..26ff3c9 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.builder;
 
 import com.android.annotations.NonNull;
+import com.android.builder.signing.SigningConfig;
 import com.google.common.base.Objects;
 
 /**
index beebd34..44757ad 100644 (file)
@@ -20,6 +20,7 @@ import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.annotations.VisibleForTesting;
 import com.android.builder.resources.ResourceSet;
+import com.android.builder.signing.SigningConfig;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
index 48addf4..4035969 100644 (file)
@@ -19,13 +19,12 @@ package com.android.builder.internal.packaging;
 import com.android.SdkConstants;
 import com.android.annotations.NonNull;
 import com.android.builder.internal.packaging.JavaResourceProcessor.IArchiveBuilder;
-import com.android.builder.internal.signing.DebugKeyProvider;
-import com.android.builder.internal.signing.SignedJarBuilder;
-import com.android.builder.internal.signing.SignedJarBuilder.IZipEntryFilter;
-import com.android.builder.internal.signing.CertificateInfo;
 import com.android.builder.packaging.DuplicateFileException;
 import com.android.builder.packaging.PackagerException;
 import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.signing.CertificateInfo;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter;
 import com.android.utils.ILogger;
 
 import java.io.File;
@@ -556,12 +555,4 @@ public final class Packager implements IArchiveBuilder {
             throw new FileNotFoundException(String.format("%s does not exist", file));
         }
     }
-
-    public static String getDebugKeystore() throws PackagerException {
-        try {
-            return DebugKeyProvider.getDefaultKeyStoreOsPath();
-        } catch (Exception e) {
-            throw new PackagerException(e, e.getMessage());
-        }
-    }
 }
diff --git a/builder/src/main/java/com/android/builder/internal/signing/DebugKeyHelper.java b/builder/src/main/java/com/android/builder/internal/signing/DebugKeyHelper.java
deleted file mode 100644 (file)
index c991b32..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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 com.android.builder.internal.signing;
-
-import com.android.annotations.NonNull;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.utils.ILogger;
-
-import java.io.FileNotFoundException;
-
-public class DebugKeyHelper {
-
-    private static final String PASSWORD_STRING = "android";
-    private static final String DEBUG_ALIAS = "AndroidDebugKey";
-
-    // Certificate CN value. This is a hard-coded value for the debug key.
-    // Android Market checks against this value in order to refuse applications signed with
-    // debug keys.
-    private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
-
-    /**
-     * Returns the location of the default debug keystore.
-     *
-     * @return The location of the default debug keystore.
-     * @throws AndroidLocationException if the location cannot be computed
-     */
-
-    public static String defaultDebugKeyStoreLocation() throws AndroidLocationException {
-        // this is guaranteed to either return a non null value (terminated with a platform
-        // specific separator), or throw.
-        String folder = AndroidLocation.getFolder();
-
-        return folder + "debug.keystore";
-    }
-
-    /**
-     * Creates a new store
-     * @param keyStoreLocation the location of the store
-     * @param storeType an optional keystore type, or <code>null</code> if the default is to
-     * be used.
-     * @param logger a logger object to receive the log of the creation.
-     * @throws KeytoolException
-     */
-    public static boolean createNewStore(@NonNull String keyStoreLocation,
-            String storeType, @NonNull ILogger logger) throws KeytoolException {
-
-        return KeystoreHelper.createNewStore(
-                keyStoreLocation, storeType, PASSWORD_STRING,
-                DEBUG_ALIAS, PASSWORD_STRING,
-                CERTIFICATE_DESC, 30 /* validity*/,
-                logger);
-    }
-
-    public static CertificateInfo getDebugKey(@NonNull String keyStoreLocation, String storeStype)
-            throws KeytoolException, FileNotFoundException {
-
-        return KeystoreHelper.getSigningInfo(
-                keyStoreLocation, PASSWORD_STRING, storeStype, DEBUG_ALIAS, PASSWORD_STRING);
-    }
-}
diff --git a/builder/src/main/java/com/android/builder/internal/signing/DebugKeyProvider.java b/builder/src/main/java/com/android/builder/internal/signing/DebugKeyProvider.java
deleted file mode 100644 (file)
index 8b232dc..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * 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 com.android.builder.internal.signing;
-
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.utils.ILogger;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.UnrecoverableEntryException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-
-/**
- * A provider of a dummy key to sign Android application for debugging purpose.
- * <p/>This provider uses a custom keystore to create and store a key with a known password.
- */
-public class DebugKeyProvider {
-
-    private static final String PASSWORD_STRING = "android";
-    private static final char[] PASSWORD_CHAR = PASSWORD_STRING.toCharArray();
-    private static final String DEBUG_ALIAS = "AndroidDebugKey";
-
-    // Certificate CN value. This is a hard-coded value for the debug key.
-    // Android Market checks against this value in order to refuse applications signed with
-    // debug keys.
-    private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
-
-    private KeyStore.PrivateKeyEntry mEntry;
-
-    /**
-     * Creates a provider using a keystore at the given location.
-     * <p/>The keystore, and a new random android debug key are created if they do not yet exist.
-     * <p/>Password for the store/key is <code>android</code>, and the key alias is
-     * <code>AndroidDebugKey</code>.
-     * @param osKeyStorePath the OS path to the keystore, or <code>null</code> if the default one
-     * is to be used.
-     * @param storeType an optional keystore type, or <code>null</code> if the default is to
-     * be used.
-     * @param logger the logger
-     * @throws KeytoolException If the creation of the debug key failed.
-     * @throws AndroidLocationException
-     */
-    public DebugKeyProvider(String osKeyStorePath, String storeType, ILogger logger)
-            throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
-            UnrecoverableEntryException, IOException, KeytoolException, AndroidLocationException {
-
-        if (osKeyStorePath == null) {
-            osKeyStorePath = getDefaultKeyStoreOsPath();
-        }
-
-        if (!loadKeyEntry(osKeyStorePath, storeType)) {
-            // create the store with the key
-            createNewStore(osKeyStorePath, storeType, logger);
-        }
-    }
-
-    /**
-     * Returns the OS path to the default debug keystore.
-     *
-     * @return The OS path to the default debug keystore.
-     * @throws AndroidLocationException
-     */
-    public static String getDefaultKeyStoreOsPath() throws AndroidLocationException {
-        return AndroidLocation.getFolder() + "debug.keystore";
-    }
-
-    /**
-     * Returns the debug {@link PrivateKey} to use to sign applications for debug purpose.
-     * @return the private key or <code>null</code> if its creation failed.
-     */
-    @SuppressWarnings("unused") // the thrown Exceptions are not actually thrown
-    public PrivateKey getDebugKey() throws KeyStoreException, NoSuchAlgorithmException,
-            UnrecoverableKeyException, UnrecoverableEntryException {
-        if (mEntry != null) {
-            return mEntry.getPrivateKey();
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns the debug {@link Certificate} to use to sign applications for debug purpose.
-     * @return the certificate or <code>null</code> if its creation failed.
-     */
-    @SuppressWarnings("unused") // the thrown Exceptions are not actually thrown
-    public Certificate getCertificate() throws KeyStoreException, NoSuchAlgorithmException,
-            UnrecoverableKeyException, UnrecoverableEntryException {
-        if (mEntry != null) {
-            return mEntry.getCertificate();
-        }
-
-        return null;
-    }
-
-    /**
-     * Loads the debug key from the keystore.
-     * @param osKeyStorePath the OS path to the keystore.
-     * @param storeType an optional keystore type, or <code>null</code> if the default is to
-     * be used.
-     * @return <code>true</code> if success, <code>false</code> if the keystore does not exist.
-     */
-    private boolean loadKeyEntry(String osKeyStorePath, String storeType) throws KeyStoreException,
-            NoSuchAlgorithmException, CertificateException, IOException,
-            UnrecoverableEntryException {
-        FileInputStream fis = null;
-        try {
-            KeyStore keyStore = KeyStore.getInstance(
-                    storeType != null ? storeType : KeyStore.getDefaultType());
-            fis = new FileInputStream(osKeyStorePath);
-            keyStore.load(fis, PASSWORD_CHAR);
-            mEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
-                    DEBUG_ALIAS, new KeyStore.PasswordProtection(PASSWORD_CHAR));
-        } catch (FileNotFoundException e) {
-            return false;
-        } finally {
-            if (fis != null) {
-                try {
-                    fis.close();
-                } catch (IOException e) {
-                    // pass
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Creates a new store
-     * @param osKeyStorePath the location of the store
-     * @param storeType an optional keystore type, or <code>null</code> if the default is to
-     * be used.
-     * @param logger an optional {@link ILogger} object to get the output of the keytool
-     *               process call.
-     * @throws KeyStoreException
-     * @throws NoSuchAlgorithmException
-     * @throws CertificateException
-     * @throws UnrecoverableEntryException
-     * @throws IOException
-     * @throws KeytoolException
-     */
-    private void createNewStore(String osKeyStorePath, String storeType, ILogger logger)
-            throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
-            UnrecoverableEntryException, IOException, KeytoolException {
-
-        if (KeystoreHelper.createNewStore(osKeyStorePath, storeType, PASSWORD_STRING, DEBUG_ALIAS,
-                PASSWORD_STRING, CERTIFICATE_DESC, 30 /* validity*/, logger)) {
-            loadKeyEntry(osKeyStorePath, storeType);
-        }
-    }
-}
index ae3ee44..d186bfb 100644 (file)
@@ -17,7 +17,7 @@
 package com.android.builder.packaging;
 
 import com.android.annotations.NonNull;
-import com.android.builder.internal.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
 
 import java.io.File;
 
diff --git a/builder/src/main/java/com/android/builder/packaging/SigningException.java b/builder/src/main/java/com/android/builder/packaging/SigningException.java
new file mode 100644 (file)
index 0000000..efa4b2e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.builder.packaging;
+
+/**
+ * An exception thrown when signing fails.
+ */
+public final class SigningException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public SigningException(String format, Object... args) {
+        super(String.format(format, args));
+    }
+
+    public SigningException(Throwable cause, String format, Object... args) {
+        super(String.format(format, args), cause);
+    }
+
+    public SigningException(Throwable cause) {
+        super(cause);
+    }
+}
\ No newline at end of file
  * limitations under the License.
  */
 
-package com.android.builder.internal.signing;
+package com.android.builder.signing;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.util.GrabProcessOutput;
 import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
 import com.android.sdklib.util.GrabProcessOutput.Wait;
@@ -32,29 +34,54 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 
 /**
- * A Helper to create new keystore/key.
+ * A Helper to create and read keystore/keys.
  */
 public final class KeystoreHelper {
 
+    // Certificate CN value. This is a hard-coded value for the debug key.
+    // Android Market checks against this value in order to refuse applications signed with
+    // debug keys.
+    private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
+
+
+    /**
+     * Returns the location of the default debug keystore.
+     *
+     * @return The location of the default debug keystore.
+     * @throws AndroidLocationException if the location cannot be computed
+     */
+    public static String defaultDebugKeystoreLocation() throws AndroidLocationException {
+        //this is guaranteed to either return a non null value (terminated with a platform
+        // specific separator), or throw.
+        String folder = AndroidLocation.getFolder();
+        return folder + "debug.keystore";
+    }
+
+    /**
+     * Creates a new debug store with the location, keyalias, and passwords specified in the
+     * config.
+     *
+     * @param signingConfig The signing config
+     * @param logger a logger object to receive the log of the creation.
+     * @throws KeytoolException
+     */
+    public static boolean createDebugStore(@NonNull SigningConfig signingConfig,
+                                           @NonNull ILogger logger) throws KeytoolException {
+
+        return createNewStore(signingConfig, CERTIFICATE_DESC, 30 /* validity*/, logger);
+    }
+
     /**
      * Creates a new store
-     * @param osKeyStorePath the location of the store
-     * @param storeType an optional keystore type, or <code>null</code> if the default is to
-     * be used.
-     * @param storePassword
-     * @param alias
-     * @param keyPassword
-     * @param description
+     *
+     * @param signingConfig the Signing Configuration
+     * @param description description
      * @param validityYears
      * @param logger
      * @throws KeytoolException
      */
-    public static boolean createNewStore(
-            @NonNull String osKeyStorePath,
-            String storeType,
-            @NonNull String storePassword,
-            @NonNull String alias,
-            @NonNull String keyPassword,
+    private static boolean createNewStore(
+            @NonNull SigningConfig signingConfig,
             @NonNull String description,
             int validityYears,
             @NonNull final ILogger logger)
@@ -81,7 +108,7 @@ public final class KeystoreHelper {
         commandList.add(keytoolCommand);
         commandList.add("-genkey");
         commandList.add("-alias");
-        commandList.add(alias);
+        commandList.add(signingConfig.getKeyAlias());
         commandList.add("-keyalg");
         commandList.add("RSA");
         commandList.add("-dname");
@@ -89,14 +116,14 @@ public final class KeystoreHelper {
         commandList.add("-validity");
         commandList.add(Integer.toString(validityYears * 365));
         commandList.add("-keypass");
-        commandList.add(keyPassword);
+        commandList.add(signingConfig.getKeyPassword());
         commandList.add("-keystore");
-        commandList.add(osKeyStorePath);
+        commandList.add(signingConfig.getStoreLocation());
         commandList.add("-storepass");
-        commandList.add(storePassword);
-        if (storeType != null) {
+        commandList.add(signingConfig.getStorePassword());
+        if (signingConfig.getStoreType() != null) {
             commandList.add("-storetype");
-            commandList.add(storeType);
+            commandList.add(signingConfig.getStoreType());
         }
 
         String[] commandArray = commandList.toArray(new String[commandList.size()]);
@@ -154,32 +181,43 @@ public final class KeystoreHelper {
         return result == 0;
     }
 
-    public static CertificateInfo getSigningInfo(
-            @NonNull String keyStoreLocation,
-            @NonNull String keyStorePassword,
-            String keyStoreType,
-            @NonNull String keyAlias,
-            @NonNull String keyPassword) throws KeytoolException, FileNotFoundException {
+    /**
+     * Returns the CertificateInfo for the given signing configuration.
+     *
+     * Returns null if the key could not be found. If the passwords are wrong,
+     * it throws an exception
+     *
+     * @param signingConfig the signing configuration
+     * @return the certificate info if it could be loaded.
+     * @throws KeytoolException
+     * @throws FileNotFoundException
+     */
+    public static CertificateInfo getCertificateInfo(SigningConfig signingConfig)
+            throws KeytoolException, FileNotFoundException {
 
         try {
             KeyStore keyStore = KeyStore.getInstance(
-                    keyStoreType != null ? keyStoreType : KeyStore.getDefaultType());
+                    signingConfig.getStoreType() != null ?
+                            signingConfig.getStoreType() : KeyStore.getDefaultType());
 
-            FileInputStream fis = new FileInputStream(keyStoreLocation);
-            keyStore.load(fis, keyStorePassword.toCharArray());
+            FileInputStream fis = new FileInputStream(signingConfig.getStoreLocation());
+            keyStore.load(fis, signingConfig.getStorePassword().toCharArray());
             fis.close();
             PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
-                    keyAlias, new KeyStore.PasswordProtection(keyPassword.toCharArray()));
+                    signingConfig.getKeyAlias(),
+                    new KeyStore.PasswordProtection(signingConfig.getKeyPassword().toCharArray()));
 
             if (entry != null) {
-                return new CertificateInfo(entry.getPrivateKey(), (X509Certificate) entry.getCertificate());
+                return new CertificateInfo(entry.getPrivateKey(),
+                        (X509Certificate) entry.getCertificate());
             }
         } catch (FileNotFoundException e) {
             throw e;
         } catch (Exception e) {
             throw new KeytoolException(
                     String.format("Failed to read key %1$s from store \"%2$s\": %3$s",
-                            keyAlias, keyStoreLocation, e.getMessage()),
+                            signingConfig.getKeyAlias(), signingConfig.getKeyPassword(),
+                            e.getMessage()),
                     e);
         }
 
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.builder.internal.signing;
+package com.android.builder.signing;
 
 public class KeytoolException extends Exception {
     /** default serial uid */
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.builder.internal.signing;
+package com.android.builder.signing;
 
-import com.android.builder.internal.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
 import sun.misc.BASE64Encoder;
 import sun.security.pkcs.ContentInfo;
 import sun.security.pkcs.PKCS7;
  * limitations under the License.
  */
 
-package com.android.builder;
+package com.android.builder.signing;
 
-import com.android.builder.internal.signing.DebugKeyHelper;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.google.common.base.Objects;
 
+import java.security.KeyStore;
+
 /**
  * SigningConfig encapsulates the information necessary to access certificates in a keystore file
  * that can be used to sign APKs.
  */
 public class SigningConfig {
 
+    public static final String DFAULT_PASSWORD = "android";
+    public static final String DEFAULT_ALIAS = "AndroidDebugKey";
+
     private String mStoreLocation = null;
     private String mStorePassword = null;
     private String mKeyAlias = null;
     private String mKeyPassword = null;
+    private String mStoreType = KeyStore.getDefaultType();
 
     /**
      * Creates a SigningConfig.
@@ -43,10 +48,10 @@ public class SigningConfig {
      * @throws AndroidLocationException if the debug keystore location cannot be found
      */
     public void initDebug() throws AndroidLocationException {
-        mStoreLocation = DebugKeyHelper.defaultDebugKeyStoreLocation();
-        mStorePassword = "android";
-        mKeyAlias = "androiddebugkey";
-        mKeyPassword = "android";
+        mStoreLocation = KeystoreHelper.defaultDebugKeystoreLocation();
+        mStorePassword = DFAULT_PASSWORD;
+        mKeyAlias = DEFAULT_ALIAS;
+        mKeyPassword = DFAULT_PASSWORD;
     }
 
     public String getStoreLocation() {
@@ -85,6 +90,14 @@ public class SigningConfig {
         return this;
     }
 
+    public String getStoreType() {
+        return mStoreType;
+    }
+
+    public void SetStoreType(String storeType) {
+        mStoreType = storeType;
+    }
+
     public boolean isSigningReady() {
         return mStoreLocation != null &&
                 mStorePassword != null &&
@@ -116,6 +129,10 @@ public class SigningConfig {
                 !mStorePassword.equals(that.mStorePassword) :
                 that.mStorePassword != null)
             return false;
+        if (mStoreType != null ?
+                !mStoreType.equals(that.mStoreType) :
+                that.mStoreType != null)
+            return false;
 
         return true;
     }
@@ -129,6 +146,7 @@ public class SigningConfig {
                 mStorePassword.hashCode() : 0);
         result = 31 * result + (mKeyAlias != null ? mKeyAlias.hashCode() : 0);
         result = 31 * result + (mKeyPassword != null ? mKeyPassword.hashCode() : 0);
+        result = 31 * result + (mStoreType != null ? mStoreType.hashCode() : 0);
         return result;
     }
 
@@ -139,6 +157,7 @@ public class SigningConfig {
                 .add("storePassword", mStorePassword)
                 .add("keyAlias", mKeyAlias)
                 .add("keyPassword", mKeyPassword)
+                .add("storeType", mStoreType)
                 .toString();
     }
 }
index 78f182d..02b79c1 100644 (file)
@@ -25,8 +25,8 @@ import junit.framework.TestCase;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.regex.Matcher;
 import java.util.Map;
+import java.util.regex.Matcher;
 
 public class FileManagerTest extends TestCase {
 
  * limitations under the License.
  */
 
-package com.android.builder.internal.signing;
+package com.android.builder.signing;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.utils.ILogger;
+import com.google.common.io.Files;
 import junit.framework.TestCase;
 
 import java.io.File;
@@ -26,56 +27,37 @@ import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 import java.util.Calendar;
 
-public class DebugKeyProviderTest extends TestCase {
-
-    private File mTmpFile;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // We want to allocate a new tmp file but not have it actually exist
-        mTmpFile = File.createTempFile(this.getClass().getSimpleName(), ".keystore");
-        assertTrue(mTmpFile.delete());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (mTmpFile != null) {
-            if (!mTmpFile.delete()) {
-                mTmpFile.deleteOnExit();
-            }
-            mTmpFile = null;
-        }
-    }
+public class KeyStoreHelperTest extends TestCase {
 
     public void testCreateAndCheckKey() throws Exception {
-        String osPath = mTmpFile.getAbsolutePath();
+        File tempFolder = Files.createTempDir();
+        File keystoreFile = new File(tempFolder, "debug.keystore");
+        keystoreFile.deleteOnExit();
 
         FakeLogger fakeLogger = new FakeLogger();
 
+        // create a default signing Config.
+        SigningConfig signingConfig = new SigningConfig();
+        signingConfig.initDebug();
+        signingConfig.setStoreLocation(keystoreFile.getAbsolutePath());
+
         // "now" is just slightly before the key was created
         long now = System.currentTimeMillis();
 
-        DebugKeyProvider provider;
-        try {
-            provider = new DebugKeyProvider(osPath,  null /*storeType*/, fakeLogger);
-        } catch (Throwable t) {
-            // In case we get any kind of exception, rewrap it to make sure we output
-            // the path used.
-            String msg = String.format("%1$s in %2$s\n%3$s",
-                    t.getClass().getSimpleName(), osPath, t.toString());
-            throw new Exception(msg, t);
-        }
-        assertNotNull(provider);
+        // create the keystore
+        KeystoreHelper.createDebugStore(signingConfig, fakeLogger);
+
+        // read the key back
+        CertificateInfo certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig);
+
+        assertNotNull(certificateInfo);
 
         assertEquals("", fakeLogger.getErr());
 
-        PrivateKey key = provider.getDebugKey();
+        PrivateKey key = certificateInfo.getKey();
         assertNotNull(key);
 
-        X509Certificate certificate = (X509Certificate) provider.getCertificate();
+        X509Certificate certificate = certificateInfo.getCertificate();
         assertNotNull(certificate);
 
         // The "not-after" (a.k.a. expiration) date should be after "now"
index 0ce59b1..7456a28 100644 (file)
@@ -17,7 +17,7 @@ package com.android.build.gradle
 
 import com.android.builder.BuildType
 import com.android.builder.ProductFlavor
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Action
 import org.gradle.api.NamedDomainObjectContainer
 import org.gradle.api.internal.project.ProjectInternal
index 046933d..27ce914 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 package com.android.build.gradle
+
 import com.android.build.gradle.internal.BuildTypeData
 import com.android.build.gradle.internal.DefaultBuildVariant
 import com.android.build.gradle.internal.ProductFlavorData
@@ -35,8 +36,8 @@ import com.android.builder.AndroidDependency
 import com.android.builder.BuildType
 import com.android.builder.BuilderConstants
 import com.android.builder.JarDependency
-import com.android.builder.SigningConfig
 import com.android.builder.VariantConfiguration
+import com.android.builder.signing.SigningConfig
 import com.google.common.collect.ArrayListMultimap
 import com.google.common.collect.ListMultimap
 import org.gradle.api.Project
@@ -47,6 +48,7 @@ import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.internal.reflect.Instantiator
 
 import javax.inject.Inject
+
 /**
  * Gradle plugin class for 'application' projects.
  */
index a80f115..c7a44b4 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 package com.android.build.gradle
+
 import com.android.SdkConstants
 import com.android.build.gradle.internal.ApplicationVariant
 import com.android.build.gradle.internal.LoggerWrapper
@@ -25,6 +26,7 @@ import com.android.build.gradle.internal.dependency.ConfigurationDependencies
 import com.android.build.gradle.internal.dependency.DependencyChecker
 import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
 import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
 import com.android.build.gradle.internal.tasks.AidlCompileTask
 import com.android.build.gradle.internal.tasks.AndroidDependencyTask
 import com.android.build.gradle.internal.tasks.AndroidTestTask
@@ -42,6 +44,7 @@ import com.android.build.gradle.internal.tasks.ProcessTestManifestTask
 import com.android.build.gradle.internal.tasks.TestFlavorTask
 import com.android.build.gradle.internal.tasks.TestLibraryTask
 import com.android.build.gradle.internal.tasks.UninstallTask
+import com.android.build.gradle.internal.tasks.ValidateSigningTask
 import com.android.build.gradle.internal.tasks.ZipAlignTask
 import com.android.builder.AndroidBuilder
 import com.android.builder.AndroidDependency
@@ -54,6 +57,7 @@ import com.android.builder.SdkParser
 import com.android.builder.SourceProvider
 import com.android.builder.SymbolFileProvider
 import com.android.builder.VariantConfiguration
+import com.android.builder.signing.SigningConfig
 import com.android.utils.ILogger
 import com.google.common.collect.ArrayListMultimap
 import com.google.common.collect.Lists
@@ -77,6 +81,7 @@ import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.tooling.BuildException
 import org.gradle.util.GUtil
+
 /**
  * Base class for all Android plugins
  */
@@ -94,6 +99,7 @@ public abstract class BasePlugin {
 
     final List<ApplicationVariant> variants = []
     final Map<AndroidDependencyImpl, PrepareLibraryTask> prepareTaskMap = [:]
+    final Map<SigningConfig, ValidateSigningTask> validateSigningTaskMap = [:]
 
     protected Project project
     protected File sdkDir
@@ -680,16 +686,20 @@ public abstract class BasePlugin {
 
         packageApp.conventionMapping.debugJni = { config.buildType.debugJniBuild }
 
-
-        if (config.signingConfig) {
-            packageApp.conventionMapping.signingStoreLocation = {
-                project.file(config.signingConfig.storeLocation)
+        SigningConfigDsl sc = config.signingConfig
+        packageApp.conventionMapping.signingConfig = { sc }
+        if (sc != null) {
+            ValidateSigningTask validateSigningTask = validateSigningTaskMap.get(sc)
+            if (validateSigningTask == null) {
+                validateSigningTask = project.tasks.add("validate${sc.name.capitalize()}Signing",
+                    ValidateSigningTask)
+                validateSigningTask.plugin = this
+                validateSigningTask.signingConfig = sc
+
+                validateSigningTaskMap.put(sc, validateSigningTask)
             }
-            packageApp.conventionMapping.signingStorePassword = {
-                config.signingConfig.storePassword
-            }
-            packageApp.conventionMapping.signingKeyAlias = { config.signingConfig.keyAlias }
-            packageApp.conventionMapping.signingKeyPassword = { config.signingConfig.keyPassword }
+
+            packageApp.dependsOn validateSigningTask
         }
 
         def signedApk = variant.isSigned()
index c6d8611..fccb721 100644 (file)
@@ -27,8 +27,8 @@ import com.android.build.gradle.tasks.ProcessManifest
 import com.android.build.gradle.tasks.ProcessResources
 import com.android.build.gradle.tasks.ZipAlign
 import com.android.builder.BuildType
-import com.android.builder.SigningConfig
 import com.android.builder.ProductFlavor
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Task
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Zip
index 75ba89b..7b7d6ff 100644 (file)
@@ -19,7 +19,7 @@ import com.android.build.gradle.internal.dsl.BuildTypeDsl
 import com.android.build.gradle.internal.dsl.SigningConfigDsl
 import com.android.builder.BuildType
 import com.android.builder.BuilderConstants
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Action
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.internal.reflect.Instantiator
index 13f869d..d4b5864 100644 (file)
  * limitations under the License.
  */
 package com.android.build.gradle
+
 import com.android.build.gradle.internal.BuildTypeData
 import com.android.build.gradle.internal.DefaultBuildVariant
 import com.android.build.gradle.internal.ProductFlavorData
 import com.android.build.gradle.internal.ProductionAppVariant
 import com.android.build.gradle.internal.TestAppVariant
 import com.android.build.gradle.internal.dependency.ConfigurationDependencies
-import com.android.build.gradle.internal.test.PluginHolder
 import com.android.builder.AndroidDependency
 import com.android.builder.BuilderConstants
 import com.android.builder.BundleDependency
@@ -37,6 +37,7 @@ import org.gradle.api.tasks.bundling.Zip
 import org.gradle.internal.reflect.Instantiator
 
 import javax.inject.Inject
+
 /**
  * Gradle plugin class for 'library' projects.
  */
index 2501d05..5cade37 100644 (file)
@@ -26,7 +26,7 @@ import com.android.build.gradle.tasks.ProcessResources
 import com.android.build.gradle.tasks.ZipAlign
 import com.android.builder.BuildType
 import com.android.builder.ProductFlavor
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Task
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Zip
index b283c8a..848bc49 100644 (file)
@@ -19,7 +19,7 @@ package com.android.build.gradle.internal.dsl
 import com.android.annotations.NonNull
 import com.android.builder.BuildType
 import com.android.builder.BuilderConstants
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 
 /**
  * DSL overlay to make methods that accept String... work.
index dee058f..955a3f7 100644 (file)
@@ -18,20 +18,23 @@ package com.android.build.gradle.internal.dsl;
 
 import com.android.annotations.NonNull;
 import com.android.builder.BuilderConstants;
-import com.android.builder.SigningConfig;
+import com.android.builder.signing.SigningConfig;
 import com.android.prefs.AndroidLocation;
 import com.google.common.base.Objects;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
 import org.gradle.tooling.BuildException;
 
+import java.io.File;
 import java.io.Serializable;
 
 /**
  * DSL overlay for {@link SigningConfig}.
-
  */
 public class SigningConfigDsl extends SigningConfig implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    @NonNull
     private final String name;
 
     /**
@@ -65,6 +68,50 @@ public class SigningConfigDsl extends SigningConfig implements Serializable {
         return this;
     }
 
+    /**
+     * Custom store location getter to annotate it with Gradle's input annotation.
+     */
+    @InputFile
+    public File getStoreFile() {
+        return new File(getStoreLocation());
+    }
+
+    /**
+     * Store password getter override to annotate it with Gradle's input annotation.
+     */
+    @Override
+    @Input
+    public String getStorePassword() {
+        return super.getStorePassword();
+    }
+
+    /**
+     * Key alias getter override to annotate it with Gradle's input annotation.
+     */
+    @Override
+    @Input
+    public String getKeyAlias() {
+        return super.getKeyAlias();
+    }
+
+    /**
+     * Key password getter override to annotate it with Gradle's input annotation.
+     */
+    @Override
+    @Input
+    public String getKeyPassword() {
+        return super.getKeyPassword();
+    }
+
+    /**
+     * Store Type getter override to annotate it with Gradle's input annotation.
+     */
+    @Override
+    @Input
+    public String getStoreType() {
+        return super.getStoreType();
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -93,6 +140,7 @@ public class SigningConfigDsl extends SigningConfig implements Serializable {
                 .add("storePassword", getStorePassword())
                 .add("keyAlias", getKeyAlias())
                 .add("keyPassword", getKeyPassword())
+                .add("storeType", getStoreFile())
                 .toString();
     }
 }
index 99f7438..57af98e 100644 (file)
@@ -16,7 +16,7 @@
 
 package com.android.build.gradle.internal.dsl
 
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.NamedDomainObjectFactory
 import org.gradle.internal.reflect.Instantiator
 
index 6b7bb9b..2caa731 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.build.gradle.internal.tasks
+
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
 import com.android.build.gradle.tasks.PackageApplication
 import com.android.builder.packaging.DuplicateFileException
 import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
 import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Nested
 import org.gradle.api.tasks.Optional
+import org.gradle.tooling.BuildException
 
 public class PackageApplicationTask extends PackageApplication {
 
@@ -29,17 +33,8 @@ public class PackageApplicationTask extends PackageApplication {
     @Input
     boolean debugJni
 
-    @InputFile @Optional
-    File signingStoreLocation
-
-    @Input @Optional
-    String signingStorePassword
-
-    @Input @Optional
-    String signingKeyAlias
-
-    @Input @Optional
-    String signingKeyPassword
+    @Nested @Optional
+    SigningConfigDsl signingConfig
 
     @Override
     protected void doFullTaskAction() {
@@ -51,10 +46,7 @@ public class PackageApplicationTask extends PackageApplication {
                     getJavaResourceDir()?.absolutePath,
                     getJniDir()?.absolutePath,
                     getDebugJni(),
-                    getSigningStoreLocation(),
-                    getSigningStorePassword(),
-                    getSigningKeyAlias(),
-                    getSigningKeyPassword(),
+                    getSigningConfig(),
                     getOutputFile().absolutePath)
         } catch (DuplicateFileException e) {
             def logger = getLogger()
@@ -62,7 +54,9 @@ public class PackageApplicationTask extends PackageApplication {
             logger.error("\tPath in archive: " + e.archivePath)
             logger.error("\tOrigin 1: " + e.file1)
             logger.error("\tOrigin 2: " + e.file2)
-            throw new RuntimeException();
+            throw new BuildException(e.getMessage(), e);
+        } catch (Exception e) {
+            throw new BuildException(e.getMessage(), e);
         }
     }
 }
diff --git a/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy b/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy
new file mode 100644 (file)
index 0000000..4edb589
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 com.android.build.gradle.internal.tasks
+
+import com.android.builder.signing.KeystoreHelper
+import com.android.builder.signing.SigningConfig
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import org.gradle.tooling.BuildException
+/**
+ * A validate task that creates the debug keystore if it's missing.
+ * It only creates it if it's in the default debug keystore location.
+ *
+ * It's linked to a given SigningConfig
+ *
+ */
+class ValidateSigningTask extends BaseTask {
+
+    SigningConfig signingConfig
+
+    /**
+     * Annotated getter for task input.
+     *
+     * This is an Input and not an InputFile because the file might not exist.
+     *
+     * @return the path of the keystore.
+     */
+    @Input
+    String getLocation() {
+        return signingConfig.getStoreLocation()
+    }
+
+    @TaskAction
+    void validate() {
+
+        String storeLocation = getLocation()
+        File f = new File(storeLocation)
+        if (!f.exists()) {
+            if (KeystoreHelper.defaultDebugKeystoreLocation().equals(storeLocation)) {
+                getLogger().info("Creating default debug keystore at %s" + storeLocation)
+                if (!KeystoreHelper.createDebugStore(signingConfig, plugin.getLogger())) {
+                    throw new BuildException("Unable to recreate missing debug keystore.");
+                }
+            }
+        }
+    }
+}
index acf785e..89d1fad 100644 (file)
@@ -21,8 +21,8 @@ import com.android.build.gradle.internal.test.BaseTest
 import com.android.build.gradle.internal.test.PluginHolder
 import com.android.builder.BuildType
 import com.android.builder.BuilderConstants
-import com.android.builder.SigningConfig
-import com.android.builder.internal.signing.DebugKeyHelper
+import com.android.builder.signing.KeystoreHelper
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Project
 import org.gradle.testfixtures.ProjectBuilder
 
@@ -314,7 +314,7 @@ public class AppPluginInternalTest extends BaseTest {
         assertNotNull(variant)
         signingConfig = variant.config.signingConfig
         assertNotNull(signingConfig)
-        assertEquals(DebugKeyHelper.defaultDebugKeyStoreLocation(), signingConfig.storeLocation)
+        assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeLocation)
 
         variant = findVariant(variants, "Flavor1Staging")
         assertNotNull(variant)
@@ -331,7 +331,7 @@ public class AppPluginInternalTest extends BaseTest {
         assertNotNull(variant)
         signingConfig = variant.config.signingConfig
         assertNotNull(signingConfig)
-        assertEquals(DebugKeyHelper.defaultDebugKeyStoreLocation(), signingConfig.storeLocation)
+        assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeLocation)
 
         variant = findVariant(variants, "Flavor2Staging")
         assertNotNull(variant)
index b37394e..ffa6cbc 100644 (file)
@@ -15,7 +15,7 @@
  */
 package com.android.build.gradle
 import com.android.build.gradle.internal.test.BaseTest
-import com.android.builder.SigningConfig
+import com.android.builder.signing.SigningConfig
 import org.gradle.api.Project
 import org.gradle.testfixtures.ProjectBuilder
 /**