Use AndroidBuilder to build basic APK.
Xavier Ducrohet [Wed, 22 Aug 2012 02:00:28 +0000 (19:00 -0700)]
Notable changes:
- AndroidBuilder now receives ProjectFlavorHolder and
  BuildTypeHolder. This both contains the flavor/type
  themselves but also implements PathProvider to
  provide the needed path.
- The [ProductFlavor/BuildType]Dimension implements
  the Holder classes to provide those paths
- Moved to using the AndroidBuilder. It is created
  by the ConfigureVariant task and set in the plugin
  through the AndroidBasePlugin (will be used by
  both plugins) and queried by the tasks with a simple
  AndroidBuilderProvider
- Updated the mergeManifest, crunchRes, processRes,
  dex and packageApp tasks to use the AndroidBuilder.

The tasks to create test apps haven't been touched yet.

Known issues:
- AndroidBuilderProvider will not work for builds that
  create more than one APK.
- Because some inputs are not explicit in some tasks
  (they are figured out automatically on the
  AndroidBuilder side, touching some files will not
  trigger new builds. The task's TaskInputs object
  needs to be manipulated to help with this. This
  will be needed for the inputs coming from library
  dependencies as well anyway.

Change-Id: I7f9ca428ce2048ce92ed780367db0aaf01f6350b

39 files changed:
.gitignore
builder/src/main/java/com/android/builder/AaptOptions.java
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/BuildTypeHolder.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/DefaultSdkParser.java
builder/src/main/java/com/android/builder/DexOptions.java
builder/src/main/java/com/android/builder/PathProvider.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/ProductFlavor.java
builder/src/main/java/com/android/builder/ProductFlavorHolder.java [new file with mode: 0644]
builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
builder/src/main/java/com/android/builder/signing/DebugKeyHelper.java
builder/src/test/java/com/android/builder/AndroidBuilderTest.java
builder/src/test/java/com/android/builder/BuildTypeHolderMock.java [new file with mode: 0644]
builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java [new file with mode: 0644]
builder/src/test/java/com/android/builder/ProductFlavorTest.java
builder/src/test/java/com/android/builder/samples/DefaultPathProvider.java [new file with mode: 0644]
builder/src/test/java/com/android/builder/samples/Main.java
gradle/src/main/groovy/com/android/build/gradle/AndroidBasePlugin.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/AndroidBuilderProvider.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/AndroidExtension.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidLibraryPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/AndroidLogger.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/AndroidPlugin.groovy
gradle/src/main/groovy/com/android/build/gradle/BaseAndroidTask.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/ConfigureVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/CrunchResources.groovy
gradle/src/main/groovy/com/android/build/gradle/Dex.groovy
gradle/src/main/groovy/com/android/build/gradle/GenerateBuildConfigTask.groovy [copied from gradle/src/main/groovy/com/android/build/gradle/GenerateManifest.groovy with 51% similarity]
gradle/src/main/groovy/com/android/build/gradle/GenerateTestManifest.groovy [moved from gradle/src/main/groovy/com/android/build/gradle/GenerateManifest.groovy with 86% similarity]
gradle/src/main/groovy/com/android/build/gradle/MergeManifest.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/PackageApplication.groovy
gradle/src/main/groovy/com/android/build/gradle/ProcessResources.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/ApplicationVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/BaseDimension.groovy [new file with mode: 0644]
gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeDimension.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorDimension.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/ProductionAppVariant.groovy
gradle/src/main/groovy/com/android/build/gradle/internal/TestAppVariant.groovy

index 6e31dc8..2c9c85e 100644 (file)
@@ -8,3 +8,4 @@ gradle/build
 testapps/*/build
 testapps/multiproject/*/build
 /repo
+/out
index 360d204..8baae2e 100644 (file)
@@ -19,10 +19,12 @@ package com.android.builder;
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
-public class AaptOptions {
+public class AaptOptions implements Serializable {
+    private static final long serialVersionUID = 5790194851736528545L;
 
     private String mIgnoreAssetsPattern;
     private List<String> mNoCompressList;
index 93035f7..636f6f0 100644 (file)
@@ -40,25 +40,27 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * This is the main builder class. It is given all the data to process the build (such as
- * {@link ProductFlavor}, {@link BuildType} and dependencies) and use them when doing specific
+ * {@link ProductFlavor}s, {@link BuildType} and dependencies) and use them when doing specific
  * build steps.
  *
  * To use:
  * create a builder with {@link #AndroidBuilder(SdkParser, ILogger, boolean)},
  * configure compile target with {@link #setTarget(String)}
- * configure build variant with {@link #setBuildVariant(ProductFlavor, ProductFlavor, BuildType)},
+ * configure build variant with {@link #setBuildVariant(ProductFlavorHolder, BuildTypeHolder)},
+ * optionally add flavors with {@link #addProductFlavor(ProductFlavorHolder)},
  * configure dependencies with {@link #setAndroidDependencies(List)} and
  *     {@link #setJarDependencies(List)},
  *
  * then build steps can be done with
- * {@link #generateBuildConfig(String, String)}
+ * {@link #generateBuildConfig(String, java.util.List)}
  * {@link #mergeLibraryManifests(File, List, File)}
- * {@link #processResources(String, String, String, String, String, String, String, String, String, String, String, AaptOptions)}
+ * {@link #processResources(String, String, String, String, String, AaptOptions)}
  * {@link #convertBytecode(String, String, DexOptions)}
- * {@link #packageApk(String, String, String, String, String, String, String)}
+ * {@link #packageApk(String, String, String, String)}
  *
  * Java compilation is not handled but the builder provides the runtime classpath with
  * {@link #getRuntimeClasspath()}.
@@ -73,8 +75,10 @@ public class AndroidBuilder {
 
     private IAndroidTarget mTarget;
 
-    private ProductFlavor mProductFlavor;
-    private BuildType mBuildType;
+    private BuildTypeHolder mBuildTypeHolder;
+    private ProductFlavorHolder mMainFlavorHolder;
+    private List<ProductFlavorHolder> mFlavorHolderList;
+    private ProductFlavor mMergedFlavor;
 
     private List<JarDependency> mJars;
 
@@ -137,17 +141,39 @@ public class AndroidBuilder {
     }
 
     /**
-     * Sets the build variant by providing the main and custom flavors and the build type
-     * @param mainFlavor
-     * @param productFlavor
-     * @param buildType
+     * Sets the initial build variant by providing the main flavor and the build type.
+     * @param mainFlavorHolder the main ProductFlavor
+     * @param buildTypeHolder the Build Type.
      */
     public void setBuildVariant(
-            @NonNull ProductFlavor mainFlavor,
-            @NonNull ProductFlavor productFlavor,
-            @NonNull BuildType buildType) {
-        mProductFlavor = productFlavor.mergeWith(mainFlavor);
-        mBuildType = buildType;
+            @NonNull ProductFlavorHolder mainFlavorHolder,
+            @NonNull BuildTypeHolder buildTypeHolder) {
+        mMainFlavorHolder = mainFlavorHolder;
+        mBuildTypeHolder = buildTypeHolder;
+        mMergedFlavor = mMainFlavorHolder.getProductFlavor();
+        validateMainFlavor();
+    }
+
+    /**
+     * Add a new ProductFlavor.
+     *
+     * If multiple flavors are added, the priority follows the order they are added when it
+     * comes to resolving Android resources overlays (ie earlier added flavors supersedes
+     * latter added ones).
+     *
+     * @param productFlavorHolder the ProductFlavorHolder
+     */
+    public void addProductFlavor(ProductFlavorHolder productFlavorHolder) {
+        if (mMainFlavorHolder == null) {
+            throw new IllegalArgumentException(
+                    "main flavor is null. setBuildVariant must be called first");
+        }
+        if (mFlavorHolderList == null) {
+            mFlavorHolderList = new ArrayList<ProductFlavorHolder>();
+        }
+
+        mFlavorHolderList.add(productFlavorHolder);
+        mMergedFlavor = productFlavorHolder.getProductFlavor().mergeOver(mMergedFlavor);
     }
 
     /**
@@ -192,33 +218,48 @@ public class AndroidBuilder {
         resolveIndirectLibraryDependencies(directLibraryProjects, mFlatLibraryProjects);
     }
 
+    /**
+     * Generate the BuildConfig class for the project.
+     * @param sourceOutputDir directory where to put this. This is the source folder, not the
+     *                        package folder.
+     * @param additionalLines additional lines to put in the class. These must be valid Java lines.
+     * @throws IOException
+     */
     public void generateBuildConfig(
-            @NonNull String manifestLocation,
-            @NonNull String outGenLocation,
+            @NonNull String sourceOutputDir,
             @Nullable List<String> additionalLines) throws IOException {
-        if (mProductFlavor == null || mBuildType == null) {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
             throw new IllegalArgumentException("Target not set.");
         }
 
+        File manifest = mMainFlavorHolder.getAndroidManifest();
+        String manifestLocation = manifest.getAbsolutePath();
+
         String packageName = getPackageOverride(manifestLocation);
         if (packageName == null) {
             packageName = getPackageFromManifest(manifestLocation);
         }
 
         BuildConfigGenerator generator = new BuildConfigGenerator(
-                outGenLocation, packageName, mBuildType.isDebuggable());
+                sourceOutputDir, packageName, mBuildTypeHolder.getBuildType().isDebuggable());
         generator.generate(additionalLines);
     }
 
-    public void preprocessResources(
-            @NonNull String mainResLocation,
-            @Nullable String flavorResLocation,
-            @Nullable String typeResLocation,
-            @NonNull String outResLocation) throws IOException, InterruptedException {
-        if (mProductFlavor == null || mBuildType == null) {
+    /**
+     * Pre-process resources. This crunches images and process 9-patches before they can
+     * be packaged.
+     * This is incremental.
+     *
+     * @param resOutputDir where the processed resources are stored.
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void preprocessResources(@NonNull String resOutputDir)
+            throws IOException, InterruptedException {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
@@ -238,31 +279,41 @@ public class AndroidBuilder {
             command.add("-v");
         }
 
-        if (typeResLocation != null) {
+        File typeResLocation = mBuildTypeHolder.getAndroidResources();
+        if (typeResLocation != null && typeResLocation.isDirectory()) {
             command.add("-S");
-            command.add(typeResLocation);
+            command.add(typeResLocation.getAbsolutePath());
         }
 
-        if (flavorResLocation != null) {
-            command.add("-S");
-            command.add(flavorResLocation);
+        for (ProductFlavorHolder holder : mFlavorHolderList) {
+            File flavorResLocation = holder.getAndroidResources();
+            if (flavorResLocation != null && flavorResLocation.isDirectory()) {
+                command.add("-S");
+                command.add(flavorResLocation.getAbsolutePath());
+            }
         }
 
-        command.add("-S");
-        command.add(mainResLocation);
+        File mainResLocation = mMainFlavorHolder.getAndroidResources();
+        if (mainResLocation != null && mainResLocation.isDirectory()) {
+            command.add("-S");
+            command.add(mainResLocation.getAbsolutePath());
+        }
 
         command.add("-C");
-        command.add(outResLocation);
+        command.add(resOutputDir);
 
         mCmdLineRunner.runCmdLine(command);
     }
 
-    public void mergeManifest(
-            @NonNull String mainLocation,
-            @Nullable String flavorLocation,
-            @Nullable String typeLocation,
-            @NonNull String outManifestLocation) {
-        if (mProductFlavor == null || mBuildType == null) {
+    /**
+     * Merges all the manifest from the BuildType and ProductFlavor(s) into a single manifest.
+     *
+     * TODO: figure out the order. Libraries first or buildtype/flavors first?
+     *
+     * @param outManifestLocation the output location for the merged manifest
+     */
+    public void mergeManifest(@NonNull String outManifestLocation) {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
@@ -270,40 +321,52 @@ public class AndroidBuilder {
         }
 
         try {
-            if (flavorLocation == null && typeLocation == null &&
-                    mFlatLibraryProjects.size() == 0) {
-                new FileOp().copyFile(new File(mainLocation), new File(outManifestLocation));
-            } else {
-                File appMergeOut;
-                if (mFlatLibraryProjects.size() == 0) {
-                    appMergeOut = new File(outManifestLocation);
-                } else {
-                    appMergeOut = File.createTempFile("manifestMerge", ".xml");
-                    appMergeOut.deleteOnExit();
-                }
+            File mainLocation = mMainFlavorHolder.getAndroidManifest();
+            File typeLocation = mBuildTypeHolder.getAndroidManifest();
+            if (typeLocation != null && typeLocation.isDirectory() == false) {
+                typeLocation = null;
+            }
 
-                List<File> manifests = new ArrayList<File>();
-                if (typeLocation != null) {
-                    manifests.add(new File(typeLocation));
-                }
-                if (flavorLocation != null) {
-                    manifests.add(new File(flavorLocation));
+            List<File> flavorManifests = new ArrayList<File>();
+            for (ProductFlavorHolder holder : mFlavorHolderList) {
+                File f = holder.getAndroidManifest();
+                if (f != null && f.isDirectory()) {
+                    flavorManifests.add(f);
                 }
+            }
 
-                ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger));
-                if (merger.process(
-                        appMergeOut,
-                        new File(mainLocation),
-                        manifests.toArray(new File[manifests.size()])) == false) {
-                    throw new RuntimeException();
-                }
+            // if no manifest to merge, just copy to location
+            if (typeLocation == null && flavorManifests.isEmpty() &&
+                    mFlatLibraryProjects.isEmpty()) {
+                new FileOp().copyFile(mainLocation, new File(outManifestLocation));
+            } else {
+                if (mFlatLibraryProjects.isEmpty()) {
+
+                    File appMergeOut = new File(outManifestLocation);
+
+                    List<File> manifests = new ArrayList<File>();
+                    if (typeLocation != null) {
+                        manifests.add(typeLocation);
+                    }
+                    manifests.addAll(flavorManifests);
+
+                    ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger));
+                    if (merger.process(
+                            appMergeOut,
+                            mainLocation,
+                            manifests.toArray(new File[manifests.size()])) == false) {
+                        throw new RuntimeException();
+                    }
+                } else {
+
+                    File appMergeOut = File.createTempFile("manifestMerge", ".xml");
+                    appMergeOut.deleteOnExit();
 
-                if (mFlatLibraryProjects.size() > 0) {
                     // recursively merge all manifests starting with the leaves and up toward the
                     // root (the app)
                     mergeLibraryManifests(appMergeOut, mDirectLibraryProjects,
                             new File(outManifestLocation));
-                }
+                    }
             }
         } catch (IOException e) {
             throw new RuntimeException(e);
@@ -341,19 +404,13 @@ public class AndroidBuilder {
     }
 
     public void processResources(
-            @NonNull String manifestLocation,
-            @NonNull String mainResLocation,
-            @Nullable String flavorResLocation,
-            @Nullable String typeResLocation,
-            @Nullable String crunchedResLocation,
-            @NonNull String mainAssetsLocation,
-            @Nullable String flavorAssetsLocation,
-            @Nullable String typeAssetsLocation,
-            @Nullable String outGenLocation,
-            @Nullable String outResPackageLocation,
-            @NonNull String outProguardLocation,
+            @NonNull String manifestFile,
+            @Nullable String preprocessResDir,
+            @Nullable String sourceOutputDir,
+            @Nullable String resPackageOutput,
+            @Nullable String proguardOutput,
             @NonNull AaptOptions options) throws IOException, InterruptedException {
-        if (mProductFlavor == null || mBuildType == null) {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
@@ -361,7 +418,7 @@ public class AndroidBuilder {
         }
 
         // if both output types are empty, then there's nothing to do and this is an error
-        if (outGenLocation == null && outResPackageLocation == null) {
+        if (sourceOutputDir == null && resPackageOutput == null) {
             throw new IllegalArgumentException("no output provided for aapt task");
         }
 
@@ -381,77 +438,87 @@ public class AndroidBuilder {
         command.add("-f");
         command.add("--no-crunch");
 
-
         // inputs
         command.add("-I");
         command.add(mTarget.getPath(IAndroidTarget.ANDROID_JAR));
 
         command.add("-M");
-        command.add(manifestLocation);
+        command.add(manifestFile);
 
         // TODO: handle libraries!
         boolean useOverlay =  false;
-        if (crunchedResLocation != null) {
+        if (preprocessResDir != null) {
             command.add("-S");
-            command.add(crunchedResLocation);
+            command.add(preprocessResDir);
             useOverlay = true;
         }
 
-        if (typeResLocation != null) {
+        File typeResLocation = mBuildTypeHolder.getAndroidResources();
+        if (typeResLocation != null && typeResLocation.isDirectory()) {
             command.add("-S");
-            command.add(typeResLocation);
+            command.add(typeResLocation.getAbsolutePath());
             useOverlay = true;
         }
 
-        if (flavorResLocation != null) {
-            command.add("-S");
-            command.add(flavorResLocation);
-            useOverlay = true;
+        for (ProductFlavorHolder holder : mFlavorHolderList) {
+            File flavorResLocation = holder.getAndroidResources();
+            if (flavorResLocation != null && typeResLocation.isDirectory()) {
+                command.add("-S");
+                command.add(flavorResLocation.getAbsolutePath());
+                useOverlay = true;
+            }
         }
 
+        File mainResLocation = mMainFlavorHolder.getAndroidResources();
         command.add("-S");
-        command.add(mainResLocation);
+        command.add(mainResLocation.getAbsolutePath());
 
         if (useOverlay) {
             command.add("--auto-add-overlay");
         }
 
-        if (typeAssetsLocation != null) {
-            command.add("-A");
-            command.add(typeAssetsLocation);
-        }
+        // TODO support 2+ assets folders.
+//        if (typeAssetsLocation != null) {
+//            command.add("-A");
+//            command.add(typeAssetsLocation);
+//        }
+//
+//        if (flavorAssetsLocation != null) {
+//            command.add("-A");
+//            command.add(flavorAssetsLocation);
+//        }
 
-        if (flavorAssetsLocation != null) {
+        File mainAssetsLocation = mMainFlavorHolder.getAndroidAssets();
+        if (mainAssetsLocation != null && mainAssetsLocation.isDirectory()) {
             command.add("-A");
-            command.add(flavorAssetsLocation);
+            command.add(mainAssetsLocation.getAbsolutePath());
         }
 
-        command.add("-A");
-        command.add(mainAssetsLocation);
-
         // outputs
 
-        if (outGenLocation != null) {
+        if (sourceOutputDir != null) {
             command.add("-m");
             command.add("-J");
-            command.add(outGenLocation);
+            command.add(sourceOutputDir);
         }
 
-        if (outResPackageLocation != null) {
+        if (resPackageOutput != null) {
             command.add("-F");
-            command.add(outResPackageLocation);
+            command.add(resPackageOutput);
 
-            command.add("-G");
-            command.add(outProguardLocation);
+            if (proguardOutput != null) {
+                command.add("-G");
+                command.add(proguardOutput);
+            }
         }
 
         // options controlled by build variants
 
-        if (mBuildType.isDebuggable()) {
+        if (mBuildTypeHolder.getBuildType().isDebuggable()) {
             command.add("--debug-mode");
         }
 
-        String packageOverride = getPackageOverride(manifestLocation);
+        String packageOverride = getPackageOverride(manifestFile);
         if (packageOverride != null) {
             command.add("--rename-manifest-package");
             command.add(packageOverride);
@@ -460,7 +527,7 @@ public class AndroidBuilder {
 
         boolean forceErrorOnReplace = false;
 
-        int versionCode = mProductFlavor.getVersionCode();
+        int versionCode = mMergedFlavor.getVersionCode();
         if (versionCode != -1) {
             command.add("--version-code");
             command.add(Integer.toString(versionCode));
@@ -468,7 +535,7 @@ public class AndroidBuilder {
             forceErrorOnReplace = true;
         }
 
-        String versionName = mProductFlavor.getVersionName();
+        String versionName = mMergedFlavor.getVersionName();
         if (versionName != null) {
             command.add("--version-name");
             command.add(versionName);
@@ -476,7 +543,7 @@ public class AndroidBuilder {
             forceErrorOnReplace = true;
         }
 
-        int minSdkVersion = mProductFlavor.getMinSdkVersion();
+        int minSdkVersion = mMergedFlavor.getMinSdkVersion();
         if (minSdkVersion != -1) {
             command.add("--min-sdk-version");
             command.add(Integer.toString(minSdkVersion));
@@ -484,7 +551,7 @@ public class AndroidBuilder {
             forceErrorOnReplace = true;
         }
 
-        int targetSdkVersion = mProductFlavor.getTargetSdkVersion();
+        int targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
         if (targetSdkVersion != -1) {
             command.add("--target-sdk-version");
             command.add(Integer.toString(targetSdkVersion));
@@ -516,10 +583,10 @@ public class AndroidBuilder {
     }
 
     public void convertBytecode(
-            @NonNull String classesLocation,
+            @NonNull List<String> classesLocation,
             @NonNull String outDexFile,
             @NonNull DexOptions dexOptions) throws IOException, InterruptedException {
-        if (mProductFlavor == null || mBuildType == null) {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
@@ -533,18 +600,21 @@ public class AndroidBuilder {
         String dxPath = mTarget.getPath(IAndroidTarget.DX);
         command.add(dxPath);
 
+        command.add("--dex");
+
         if (mVerboseExec) {
-            command.add("-v");
+            command.add("--verbose");
         }
 
         command.add("--output");
         command.add(outDexFile);
 
         // TODO: handle dependencies
+        // TODO: handle dex options
 
         mLogger.verbose("Input: " + classesLocation);
 
-        command.add(classesLocation);
+        command.addAll(classesLocation);
 
         mCmdLineRunner.runCmdLine(command);
     }
@@ -553,21 +623,15 @@ public class AndroidBuilder {
      * Packages the apk.
      * @param androidResPkgLocation
      * @param classesDexLocation
-     * @param mainJavaResLocation
-     * @param flavorJavaResLocation
-     * @param buildTypeJavaResLocation
      * @param jniLibsLocation
      * @param outApkLocation
      */
     public void packageApk(
             @NonNull String androidResPkgLocation,
             @NonNull String classesDexLocation,
-            @NonNull String mainJavaResLocation,
-            @Nullable String flavorJavaResLocation,
-            @Nullable String buildTypeJavaResLocation,
-            @NonNull String jniLibsLocation,
-            @NonNull String outApkLocation) {
-        if (mProductFlavor == null || mBuildType == null) {
+            @Nullable String jniLibsLocation,
+            @NonNull String outApkLocation) throws DuplicateFileException {
+        if (mMainFlavorHolder == null || mBuildTypeHolder == null) {
             throw new IllegalArgumentException("No Product Flavor or Build Type set.");
         }
         if (mTarget == null) {
@@ -575,7 +639,7 @@ public class AndroidBuilder {
         }
 
         SigningInfo signingInfo = null;
-        if (mBuildType.isDebugSigningKey()) {
+        if (mBuildTypeHolder.getBuildType().isDebugSigningKey()) {
             try {
                 String storeLocation = DebugKeyHelper.defaultDebugKeyStoreLocation();
                 File storeFile = new File(storeLocation);
@@ -610,23 +674,43 @@ public class AndroidBuilder {
                     outApkLocation, androidResPkgLocation, classesDexLocation,
                     signingInfo, mLogger);
 
-            packager.setDebugJniMode(mBuildType.isDebugJniBuild());
+            packager.setDebugJniMode(mBuildTypeHolder.getBuildType().isDebugJniBuild());
 
             // figure out conflicts!
             JavaResourceProcessor resProcessor = new JavaResourceProcessor(packager);
-            resProcessor.addSourceFolder(buildTypeJavaResLocation);
-            resProcessor.addSourceFolder(flavorJavaResLocation);
-            resProcessor.addSourceFolder(mainJavaResLocation);
 
-            // also add resources from library projects and jars
+            Set<File> buildTypeJavaResLocations = mBuildTypeHolder.getJavaResources();
+            for (File buildTypeJavaResLocation : buildTypeJavaResLocations) {
+                if (buildTypeJavaResLocation != null && buildTypeJavaResLocation.isDirectory()) {
+                    resProcessor.addSourceFolder(buildTypeJavaResLocation.getAbsolutePath());
+                }
+            }
 
-            packager.addNativeLibraries(jniLibsLocation);
+            for (ProductFlavorHolder holder : mFlavorHolderList) {
+
+                Set<File> flavorJavaResLocations = holder.getJavaResources();
+                for (File flavorJavaResLocation : flavorJavaResLocations) {
+                    if (flavorJavaResLocation != null && flavorJavaResLocation.isDirectory()) {
+                        resProcessor.addSourceFolder(flavorJavaResLocation.getAbsolutePath());
+                    }
+                }
+            }
+
+            Set<File> mainJavaResLocations = mMainFlavorHolder.getJavaResources();
+            for (File mainJavaResLocation : mainJavaResLocations) {
+                if (mainJavaResLocation != null && mainJavaResLocation.isDirectory()) {
+                    resProcessor.addSourceFolder(mainJavaResLocation.getAbsolutePath());
+                }
+            }
+
+            // also add resources from library projects and jars
+            if (jniLibsLocation != null) {
+                packager.addNativeLibraries(jniLibsLocation);
+            }
 
             packager.sealApk();
         } catch (PackagerException e) {
             throw new RuntimeException(e);
-        } catch (DuplicateFileException e) {
-            throw new RuntimeException(e);
         } catch (SealedPackageException e) {
             throw new RuntimeException(e);
         }
@@ -634,8 +718,8 @@ public class AndroidBuilder {
 
     @VisibleForTesting
     String getPackageOverride(@NonNull String manifestLocation) {
-        String packageName = mProductFlavor.getPackageName();
-        String packageSuffix = mBuildType.getPackageNameSuffix();
+        String packageName = mMergedFlavor.getPackageName();
+        String packageSuffix = mBuildTypeHolder.getBuildType().getPackageNameSuffix();
 
         if (packageSuffix != null) {
             if (packageName == null) {
@@ -685,4 +769,12 @@ public class AndroidBuilder {
         }
     }
 
+    protected void validateMainFlavor() {
+        File manifest = mMainFlavorHolder.getAndroidManifest();
+        if (!manifest.isFile()) {
+            throw new IllegalArgumentException(
+                    "Main Manifest missing from " + manifest.getAbsolutePath());
+        }
+    }
+
 }
index cba7a4a..ba10374 100644 (file)
@@ -29,6 +29,7 @@ public class BuildType {
     private boolean mDebugJniBuild;
     private boolean mDebugSigningKey;
     private String mPackageNameSuffix = null;
+    private boolean mRunProguard = false;
 
     private boolean mZipAlign = true;
 
@@ -90,6 +91,14 @@ public class BuildType {
         return mPackageNameSuffix;
     }
 
+    public void setRunProguard(boolean runProguard) {
+        mRunProguard = runProguard;
+    }
+
+    public boolean isRunProguard() {
+        return mRunProguard;
+    }
+
     public void setZipAlign(boolean zipAlign) {
         mZipAlign = zipAlign;
     }
diff --git a/builder/src/main/java/com/android/builder/BuildTypeHolder.java b/builder/src/main/java/com/android/builder/BuildTypeHolder.java
new file mode 100644 (file)
index 0000000..e74dc92
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Holds a BuildType and gives access to its various paths
+ */
+public interface BuildTypeHolder extends PathProvider {
+
+    BuildType getBuildType();
+}
index ff8843d..8c118ce 100644 (file)
@@ -22,6 +22,8 @@ import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkManager;
 import com.android.utils.ILogger;
 
+import java.io.File;
+
 /**
  * Default implementation of {@link SdkParser} for a normal Android SDK distribution.
  */
@@ -30,7 +32,11 @@ public class DefaultSdkParser implements SdkParser {
     private final String mSdkLocation;
 
     public DefaultSdkParser(@NonNull String sdkLocation) {
-        mSdkLocation = sdkLocation;
+        if (!sdkLocation.endsWith(File.separator)) {
+            mSdkLocation = sdkLocation + File.separator;
+        } else {
+            mSdkLocation = sdkLocation;
+        }
     }
 
     @Override
index a686f39..cee4fc2 100644 (file)
@@ -16,6 +16,9 @@
 
 package com.android.builder;
 
-public class DexOptions {
+import java.io.Serializable;
+
+public class DexOptions implements Serializable {
+    private static final long serialVersionUID = -1484459443169056493L;
 
 }
diff --git a/builder/src/main/java/com/android/builder/PathProvider.java b/builder/src/main/java/com/android/builder/PathProvider.java
new file mode 100644 (file)
index 0000000..0053cdb
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Provides paths for configuration (either ProductFlavor or BuildType)
+ */
+public interface PathProvider {
+
+    Set<File> getJavaSource();
+    Set<File> getJavaResources();
+
+    File getAndroidResources();
+    File getAndroidAssets();
+    File getAndroidManifest();
+
+    File getAidlSource();
+    File getRenderscriptSource();
+    File getNativeSource();
+}
index 4d8cc0b..85aecba 100644 (file)
@@ -95,10 +95,10 @@ public class ProductFlavor {
 
     /**
      * Merges the flavor on top of a base platform and returns a new object with the result.
-     * @param base
-     * @return
+     * @param base the flavor to merge on top of
+     * @return a new merged product flavor
      */
-    ProductFlavor mergeWith(@NonNull ProductFlavor base) {
+    ProductFlavor mergeOver(@NonNull ProductFlavor base) {
         ProductFlavor flavor = new ProductFlavor("");
 
         flavor.mMinSdkVersion = chooseInt(mMinSdkVersion, base.mMinSdkVersion);
diff --git a/builder/src/main/java/com/android/builder/ProductFlavorHolder.java b/builder/src/main/java/com/android/builder/ProductFlavorHolder.java
new file mode 100644 (file)
index 0000000..a95930d
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Holds a ProductFlavor and gives access to its various paths
+ */
+public interface ProductFlavorHolder extends PathProvider {
+
+    ProductFlavor getProductFlavor();
+}
index 107422d..d186bfb 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.builder.packaging;
 
+import com.android.annotations.NonNull;
 import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
 
 import java.io.File;
@@ -29,7 +30,8 @@ public final class DuplicateFileException extends ZipAbortException {
     private final File mFile1;
     private final File mFile2;
 
-    public DuplicateFileException(String archivePath, File file1, File file2) {
+    public DuplicateFileException(@NonNull String archivePath, @NonNull File file1,
+                                  @NonNull File file2) {
         super();
         mArchivePath = archivePath;
         mFile1 = file1;
@@ -50,6 +52,6 @@ public final class DuplicateFileException extends ZipAbortException {
 
     @Override
     public String getMessage() {
-        return "Duplicate files at the same path inside the APK";
+        return "Duplicate files at the same path inside the APK: " + mArchivePath;
     }
 }
\ No newline at end of file
index 72813b8..6f26c54 100644 (file)
@@ -70,6 +70,6 @@ public class DebugKeyHelper {
             throws KeytoolException, FileNotFoundException {
 
         return KeystoreHelper.getSigningInfo(
-                keyStoreLocation, PASSWORD_STRING, storeStype, PASSWORD_STRING, PASSWORD_STRING);
+                keyStoreLocation, PASSWORD_STRING, storeStype, DEBUG_ALIAS, PASSWORD_STRING);
     }
 }
index 40cc650..f4192b3 100644 (file)
 
 package com.android.builder;
 
+import com.android.annotations.NonNull;
+import com.android.utils.ILogger;
 import com.android.utils.StdLogger;
-
 import junit.framework.TestCase;
 
 public class AndroidBuilderTest extends TestCase {
 
-    private ProductFlavor mMain;
-    private ProductFlavor mFlavor;
-    private BuildType mDebug;
+    private ProductFlavorHolder mMain;
+    private ProductFlavorHolder mFlavor;
+    private BuildTypeHolder mDebug;
+
+    private static class AndroidBuilderMock extends AndroidBuilder {
+
+        public AndroidBuilderMock(@NonNull SdkParser sdkParser, ILogger logger, boolean verboseExec) {
+            super(sdkParser, logger, verboseExec);
+        }
+
+        AndroidBuilderMock(@NonNull SdkParser sdkParser, @NonNull ManifestParser manifestParser, @NonNull CommandLineRunner cmdLineRunner, @NonNull ILogger logger, boolean verboseExec) {
+            super(sdkParser, manifestParser, cmdLineRunner, logger, verboseExec);
+        }
+
+        @Override
+        protected void validateMainFlavor() {
+            // do nothing
+        }
+    }
 
     private static class ManifestParserMock implements ManifestParser {
 
@@ -42,51 +59,55 @@ public class AndroidBuilderTest extends TestCase {
 
     @Override
     protected void setUp() throws Exception {
-        mMain = new ProductFlavor("main");
-        mFlavor = new ProductFlavor("flavor");
-        mDebug = new BuildType("debug");
+        mMain = new ProductFlavorHolderMock(new ProductFlavor("main"));
+        mFlavor = new ProductFlavorHolderMock(new ProductFlavor("flavor"));
+        mDebug = new BuildTypeHolderMock(new BuildType("debug"));
     }
 
     public void testPackageOverrideNone() {
-        AndroidBuilder builder = new AndroidBuilder(new DefaultSdkParser(""),
+        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
                 new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
 
-        builder.setBuildVariant(mMain, mFlavor, mDebug);
+        builder.setBuildVariant(mMain, mDebug);
+        builder.addProductFlavor(mFlavor);
 
         assertNull(builder.getPackageOverride(""));
     }
 
     public void testPackageOverridePackageFromFlavor() {
-        AndroidBuilder builder = new AndroidBuilder(new DefaultSdkParser(""),
+        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
                 new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
 
-        mFlavor.setPackageName("foo.bar");
+        mFlavor.getProductFlavor().setPackageName("foo.bar");
 
-        builder.setBuildVariant(mMain, mFlavor, mDebug);
+        builder.setBuildVariant(mMain, mDebug);
+        builder.addProductFlavor(mFlavor);
 
         assertEquals("foo.bar", builder.getPackageOverride(""));
     }
 
     public void testPackageOverridePackageFromFlavorWithSuffix() {
-        AndroidBuilder builder = new AndroidBuilder(new DefaultSdkParser(""),
+        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
                 new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
 
-        mFlavor.setPackageName("foo.bar");
-        mDebug.setPackageNameSuffix(".fortytwo");
+        mFlavor.getProductFlavor().setPackageName("foo.bar");
+        mDebug.getBuildType().setPackageNameSuffix(".fortytwo");
 
-        builder.setBuildVariant(mMain, mFlavor, mDebug);
+        builder.setBuildVariant(mMain, mDebug);
+        builder.addProductFlavor(mFlavor);
 
         assertEquals("foo.bar.fortytwo", builder.getPackageOverride(""));
     }
 
     public void testPackageOverridePackageFromFlavorWithSuffix2() {
-        AndroidBuilder builder = new AndroidBuilder(new DefaultSdkParser(""),
+        AndroidBuilder builder = new AndroidBuilderMock(new DefaultSdkParser(""),
                 new StdLogger(StdLogger.Level.ERROR), false /*verboseExec*/);
 
-        mFlavor.setPackageName("foo.bar");
-        mDebug.setPackageNameSuffix("fortytwo");
+        mFlavor.getProductFlavor().setPackageName("foo.bar");
+        mDebug.getBuildType().setPackageNameSuffix("fortytwo");
 
-        builder.setBuildVariant(mMain, mFlavor, mDebug);
+        builder.setBuildVariant(mMain, mDebug);
+        builder.addProductFlavor(mFlavor);
 
         assertEquals("foo.bar.fortytwo", builder.getPackageOverride(""));
     }
@@ -94,16 +115,17 @@ public class AndroidBuilderTest extends TestCase {
     public void testPackageOverridePackageWithSuffixOnly() {
         StdLogger logger = new StdLogger(StdLogger.Level.ERROR);
 
-        AndroidBuilder builder = new AndroidBuilder(
+        AndroidBuilder builder = new AndroidBuilderMock(
                 new DefaultSdkParser(""),
                 new ManifestParserMock("fake.package.name"),
                 new CommandLineRunner(logger),
                 logger,
                 false /*verboseExec*/);
 
-        mDebug.setPackageNameSuffix("fortytwo");
+        mDebug.getBuildType().setPackageNameSuffix("fortytwo");
 
-        builder.setBuildVariant(mMain, mFlavor, mDebug);
+        builder.setBuildVariant(mMain, mDebug);
+        builder.addProductFlavor(mFlavor);
 
         assertEquals("fake.package.name.fortytwo", builder.getPackageOverride(""));
     }
diff --git a/builder/src/test/java/com/android/builder/BuildTypeHolderMock.java b/builder/src/test/java/com/android/builder/BuildTypeHolderMock.java
new file mode 100644 (file)
index 0000000..0e0ae48
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.Set;
+
+class BuildTypeHolderMock implements BuildTypeHolder {
+
+    private final BuildType mBuildType;
+
+    BuildTypeHolderMock(BuildType buildType) {
+        mBuildType = buildType;
+    }
+
+    @Override
+    public BuildType getBuildType() {
+        return mBuildType;
+    }
+
+    @Override
+    public Set<File> getJavaSource() {
+        return null;
+    }
+
+    @Override
+    public Set<File> getJavaResources() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidResources() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidAssets() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidManifest() {
+        return null;
+    }
+
+    @Override
+    public File getAidlSource() {
+        return null;
+    }
+
+    @Override
+    public File getRenderscriptSource() {
+        return null;
+    }
+
+    @Override
+    public File getNativeSource() {
+        return null;
+    }
+}
diff --git a/builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java b/builder/src/test/java/com/android/builder/ProductFlavorHolderMock.java
new file mode 100644 (file)
index 0000000..f887a14
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.Set;
+
+class ProductFlavorHolderMock implements ProductFlavorHolder {
+
+    private final ProductFlavor mProductFlavor;
+
+    ProductFlavorHolderMock(ProductFlavor productFlavor) {
+        mProductFlavor = productFlavor;
+    }
+
+    @Override
+    public ProductFlavor getProductFlavor() {
+        return mProductFlavor;
+    }
+
+    @Override
+    public Set<File> getJavaSource() {
+        return null;
+    }
+
+    @Override
+    public Set<File> getJavaResources() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidResources() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidAssets() {
+        return null;
+    }
+
+    @Override
+    public File getAndroidManifest() {
+        return null;
+    }
+
+    @Override
+    public File getAidlSource() {
+        return null;
+    }
+
+    @Override
+    public File getRenderscriptSource() {
+        return null;
+    }
+
+    @Override
+    public File getNativeSource() {
+        return null;
+    }
+}
index 84be8ee..87654d9 100644 (file)
@@ -40,7 +40,7 @@ public class ProductFlavorTest extends TestCase {
     }
 
     public void testMergeOnDefault() {
-        ProductFlavor flavor = mCustom.mergeWith(mDefault);
+        ProductFlavor flavor = mCustom.mergeOver(mDefault);
 
         assertEquals(42, flavor.getMinSdkVersion());
         assertEquals(43, flavor.getTargetSdkVersion());
@@ -52,7 +52,7 @@ public class ProductFlavorTest extends TestCase {
     }
 
     public void testMergeOnCustom() {
-        ProductFlavor flavor = mDefault.mergeWith(mCustom);
+        ProductFlavor flavor = mDefault.mergeOver(mCustom);
 
         assertEquals(42, flavor.getMinSdkVersion());
         assertEquals(43, flavor.getTargetSdkVersion());
@@ -64,7 +64,7 @@ public class ProductFlavorTest extends TestCase {
     }
 
     public void testMergeDefaultOnDefault() {
-        ProductFlavor flavor = mDefault.mergeWith(mDefault2);
+        ProductFlavor flavor = mDefault.mergeOver(mDefault2);
 
         assertEquals(-1, flavor.getMinSdkVersion());
         assertEquals(-1, flavor.getTargetSdkVersion());
diff --git a/builder/src/test/java/com/android/builder/samples/DefaultPathProvider.java b/builder/src/test/java/com/android/builder/samples/DefaultPathProvider.java
new file mode 100644 (file)
index 0000000..874a281
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.samples;
+
+import com.android.builder.PathProvider;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementation of PathProvider for testing that provides the default convention paths.
+ */
+public class DefaultPathProvider implements PathProvider {
+
+    private final String mRoot;
+
+    DefaultPathProvider(String root) {
+        mRoot = root;
+    }
+
+    @Override
+    public Set<File> getJavaSource() {
+        return Collections.singleton(new File(mRoot, "java"));
+    }
+
+    @Override
+    public Set<File> getJavaResources() {
+        return Collections.singleton(new File(mRoot, "resources"));
+    }
+
+    @Override
+    public File getAndroidResources() {
+        return new File(mRoot, "res");
+    }
+
+    @Override
+    public File getAndroidAssets() {
+        return new File(mRoot, "assets");
+    }
+
+    @Override
+    public File getAndroidManifest() {
+        return new File(mRoot, "AndroidManifest.xml");
+    }
+
+    @Override
+    public File getAidlSource() {
+        return new File(mRoot, "aidl");
+    }
+
+    @Override
+    public File getRenderscriptSource() {
+        return new File(mRoot, "rs");
+    }
+
+    @Override
+    public File getNativeSource() {
+        return new File(mRoot, "jni");
+    }
+}
index 2e53390..5cbc21a 100644 (file)
@@ -3,8 +3,10 @@ package com.android.builder.samples;
 import com.android.builder.AaptOptions;
 import com.android.builder.AndroidBuilder;
 import com.android.builder.BuildType;
+import com.android.builder.BuildTypeHolder;
 import com.android.builder.DefaultSdkParser;
 import com.android.builder.ProductFlavor;
+import com.android.builder.ProductFlavorHolder;
 import com.android.utils.StdLogger;
 
 import java.io.File;
@@ -13,6 +15,38 @@ import java.util.Arrays;
 
 public class Main {
 
+    private static class DefaultBuildTypeHolder extends DefaultPathProvider
+            implements BuildTypeHolder {
+
+        private final BuildType mBuildType;
+
+        DefaultBuildTypeHolder(String root, BuildType buildType) {
+            super(root + File.separatorChar + "src" + File.separatorChar + buildType.getName());
+            mBuildType = buildType;
+        }
+
+        @Override
+        public BuildType getBuildType() {
+            return mBuildType;
+        }
+    }
+
+    private static class DefaultProductFlavorHolder extends DefaultPathProvider
+            implements ProductFlavorHolder {
+
+        private final ProductFlavor mProductFlavor;
+
+        DefaultProductFlavorHolder(String root, ProductFlavor productFlavor) {
+            super(root + File.separatorChar + "src" + File.separatorChar + productFlavor.getName());
+            mProductFlavor = productFlavor;
+        }
+
+        @Override
+        public ProductFlavor getProductFlavor() {
+            return mProductFlavor;
+        }
+    }
+
     /**
      * Usage: <sdklocation> <samplelocation>
      *
@@ -38,8 +72,6 @@ public class Main {
 
         AaptOptions aaptOptions = new AaptOptions();
 
-        builder.setBuildVariant(mainFlavor, customFlavor, debug);
-
         String sample = args[1];
         String build = sample + File.separator + "build";
         checkFolder(build);
@@ -50,29 +82,26 @@ public class Main {
         String outRes = build + File.separator + "res";
         checkFolder(outRes);
 
+        DefaultProductFlavorHolder mainHolder = new DefaultProductFlavorHolder(sample, mainFlavor);
+        builder.setBuildVariant(
+                mainHolder,
+                new DefaultBuildTypeHolder(sample, debug));
+        builder.addProductFlavor(new DefaultProductFlavorHolder(sample, customFlavor));
+
+
         String[] lines = new String[] {
                 "public final static int A = 1;"
         };
         builder.generateBuildConfig(
-                sample + File.separator + "AndroidManifest.xml",
                 gen,
                 Arrays.asList(lines));
 
         builder.preprocessResources(
-                sample + File.separator + "res",
-                null, /*flavorResLocation*/
-                null, /*typeResLocation*/
                 outRes);
 
         builder.processResources(
-                sample + File.separator + "AndroidManifest.xml",
-                sample + File.separator + "res",
-                null, /*flavorResLocation*/
-                null, /*typeResLocation*/
+                mainHolder.getAndroidManifest().getAbsolutePath(),
                 outRes,
-                sample + File.separator + "assets",
-                null, /*flavorAssetsLocation*/
-                null, /*typeAssetsLocation*/
                 gen,
                 build + File.separator + "foo.apk_",
                 build + File.separator + "foo.proguard.txt",
diff --git a/gradle/src/main/groovy/com/android/build/gradle/AndroidBasePlugin.groovy b/gradle/src/main/groovy/com/android/build/gradle/AndroidBasePlugin.groovy
new file mode 100644 (file)
index 0000000..17afcc1
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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.build.gradle
+
+import com.android.builder.AndroidBuilder
+import com.android.builder.ProductFlavorHolder
+import com.android.builder.SdkParser
+import com.android.utils.ILogger
+
+/**
+ * Base class for all Android plugins
+ */
+interface AndroidBasePlugin {
+
+    SdkParser getSdkParser()
+    String getTarget()
+    ILogger getLogger()
+    boolean isVerbose()
+
+    ProductFlavorHolder getMainFlavor()
+
+    void setAndroidBuilder(AndroidBuilder androidBuilder)
+}
diff --git a/gradle/src/main/groovy/com/android/build/gradle/AndroidBuilderProvider.groovy b/gradle/src/main/groovy/com/android/build/gradle/AndroidBuilderProvider.groovy
new file mode 100644 (file)
index 0000000..3b89eb7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.build.gradle
+
+import com.android.builder.AndroidBuilder
+
+/**
+ * Gives access to an {@link AndroidBuilder}
+ */
+public interface AndroidBuilderProvider {
+
+    /**
+     * Returns an {@link AndroidBuilder}
+     * @return an {@link AndroidBuilder}
+     */
+    AndroidBuilder getAndroidBuilder()
+}
\ No newline at end of file
index 0283245..6b280bb 100644 (file)
@@ -19,16 +19,18 @@ import com.android.builder.BuildType
 import com.android.builder.ProductFlavor
 import org.gradle.api.Action
 import org.gradle.api.NamedDomainObjectContainer
+import com.android.builder.AaptOptions
+import com.android.builder.DexOptions
 
 class AndroidExtension {
     final NamedDomainObjectContainer<BuildType> buildTypes
     final NamedDomainObjectContainer<ProductFlavor> productFlavors
     String target = "android-16"
-    String packageName
-    Integer versionCode
-    String versionName
+    final AaptOptions aaptOptions = new AaptOptions()
+    final DexOptions dexOptions = new DexOptions()
 
-    AndroidExtension(NamedDomainObjectContainer<BuildType> buildTypes, NamedDomainObjectContainer<ProductFlavor> productFlavors) {
+    AndroidExtension(NamedDomainObjectContainer<BuildType> buildTypes,
+                     NamedDomainObjectContainer<ProductFlavor> productFlavors) {
         this.buildTypes = buildTypes
         this.productFlavors = productFlavors
     }
index 002ef03..b1bcc75 100644 (file)
@@ -40,8 +40,8 @@ class AndroidLibraryPlugin implements Plugin<Project> {
             throw new UnsupportedOperationException("Removing build types is not implemented yet.")
         }
 
-        buildTypes.create('debug')
-        buildTypes.create('release')
+        buildTypes.create(BuildType.DEBUG)
+        buildTypes.create(BuildType.RELEASE)
     }
 
     void addBuildType(BuildType buildType) {
diff --git a/gradle/src/main/groovy/com/android/build/gradle/AndroidLogger.groovy b/gradle/src/main/groovy/com/android/build/gradle/AndroidLogger.groovy
new file mode 100644 (file)
index 0000000..8aa86d0
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.build.gradle
+
+import com.android.utils.ILogger
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.LogLevel
+
+/**
+ * Implementation of Android's {@link ILogger} over gradle's {@link Logger}.
+ */
+class AndroidLogger implements ILogger {
+
+    private final Logger logger
+
+    AndroidLogger(Logger logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    void error(Throwable throwable, String s, Object... objects) {
+        if (throwable == null) {
+            logger.log(LogLevel.ERROR, String.format(s, objects))
+        } else {
+            logger.log(LogLevel.ERROR, String.format(s, objects), throwable)
+        }
+    }
+
+    @Override
+    void warning(String s, Object... objects) {
+        logger.log(LogLevel.WARN, s, objects)
+    }
+
+    @Override
+    void info(String s, Object... objects) {
+        logger.log(LogLevel.INFO, s, objects)
+    }
+
+    @Override
+    void verbose(String s, Object... objects) {
+        logger.log(LogLevel.DEBUG, s, objects)
+    }
+}
index 0aaa37d..d2cc280 100644 (file)
@@ -15,8 +15,6 @@
  */
 package com.android.build.gradle
 
-import org.gradle.internal.reflect.Instantiator
-
 import com.android.build.gradle.internal.AndroidManifest
 import com.android.build.gradle.internal.ApplicationVariant
 import com.android.build.gradle.internal.BuildTypeDimension
@@ -25,14 +23,20 @@ import com.android.build.gradle.internal.ProductionAppVariant
 import com.android.build.gradle.internal.TestAppVariant
 import com.android.builder.AndroidBuilder
 import com.android.builder.BuildType
+import com.android.builder.DefaultSdkParser
 import com.android.builder.ProductFlavor
+import com.android.builder.ProductFlavorHolder
+import com.android.builder.SdkParser
+import com.android.utils.ILogger
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.logging.LogLevel
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.compile.Compile
+import org.gradle.internal.reflect.Instantiator
 
-class AndroidPlugin implements Plugin<Project> {
+class AndroidPlugin implements Plugin<Project>, AndroidBasePlugin, AndroidBuilderProvider {
     private final Set<ProductionAppVariant> variants = []
     private final Map<String, BuildTypeDimension> buildTypes = [:]
     private final Map<String, ProductFlavorDimension> productFlavors = [:]
@@ -40,7 +44,9 @@ class AndroidPlugin implements Plugin<Project> {
     private SourceSet main
     private SourceSet test
     private File sdkDir
+    private DefaultSdkParser androidSdkParser
     private AndroidBuilder androidBuilder;
+    private AndroidLogger androidLogger
     private AndroidExtension extension
     private AndroidManifest mainManifest
 
@@ -57,16 +63,9 @@ class AndroidPlugin implements Plugin<Project> {
         }
 
         extension = project.extensions.create('android', AndroidExtension, buildTypes, productFlavors)
-        extension.conventionMapping.packageName = { getMainManifest().packageName }
-        extension.conventionMapping.versionCode = { getMainManifest().versionCode }
-        extension.conventionMapping.versionName = { getMainManifest().versionName }
 
         findSdk(project)
 
-        project.sourceSets.all { sourceSet ->
-            sourceSet.resources.srcDirs = ["src/$sourceSet.name/res"]
-        }
-
         main = project.sourceSets.add('main')
         test = project.sourceSets.add('test')
 
@@ -77,8 +76,8 @@ class AndroidPlugin implements Plugin<Project> {
             throw new UnsupportedOperationException("Removing build types is not implemented yet.")
         }
 
-        buildTypes.create('debug')
-        buildTypes.create('release')
+        buildTypes.create(BuildType.DEBUG)
+        buildTypes.create(BuildType.RELEASE)
 
         productFlavors.whenObjectAdded { ProductFlavor flavor ->
             addProductFlavor(flavor)
@@ -87,11 +86,7 @@ class AndroidPlugin implements Plugin<Project> {
             throw new UnsupportedOperationException("Removing product flavors is not implemented yet.")
         }
 
-        project.afterEvaluate {
-            if (productFlavors.isEmpty()) {
-                productFlavors.create('main')
-            }
-        }
+        productFlavors.create('main')
 
         project.tasks.assemble.dependsOn { variants.collect { "assemble${it.name}" } }
     }
@@ -114,7 +109,7 @@ class AndroidPlugin implements Plugin<Project> {
 
     private void findSdk(Project project) {
         def localProperties = project.file("local.properties")
-        if (localProperties) {
+        if (localProperties.exists()) {
             Properties properties = new Properties()
             localProperties.withInputStream { instr ->
                 properties.load(instr)
@@ -125,12 +120,14 @@ class AndroidPlugin implements Plugin<Project> {
             }
             sdkDir = new File(sdkDirProp)
         } else {
-            sdkDir = System.getProperty("ANDROID_HOME");
-
+            def envVar = System.getenv("ANDROID_HOME")
+            if (envVar != null) {
+                sdkDir = new File(envVar)
+            }
         }
 
         if (sdkDir == null) {
-            throw new RuntimeException("SDK location not found. Define location with sdk.dir in local.properties file or with ANDROID_HOME environment variable.")
+            throw new RuntimeException("SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.")
         }
 
         if (!sdkDir.directory) {
@@ -139,9 +136,13 @@ class AndroidPlugin implements Plugin<Project> {
     }
 
     private void addBuildType(BuildType buildType) {
+        if (buildType.name.startsWith("test")) {
+            throw new RuntimeException("BuildType names cannot start with 'test'")
+        }
+
         def sourceSet = project.sourceSets.add(buildType.name)
 
-        def buildTypeDimension = new BuildTypeDimension(buildType, sourceSet)
+        def buildTypeDimension = new BuildTypeDimension(buildType, sourceSet, project.projectDir.absolutePath)
         buildTypes[buildType.name] = buildTypeDimension
 
         def assembleBuildType = project.tasks.add(buildTypeDimension.assembleTaskName)
@@ -157,6 +158,10 @@ class AndroidPlugin implements Plugin<Project> {
     }
 
     private void addProductFlavor(ProductFlavor productFlavor) {
+        if (productFlavor.name.startsWith("test")) {
+            throw new RuntimeException("ProductFlavor names cannot start with 'test'")
+        }
+
         def mainSourceSet
         def testSourceSet
         if (productFlavor.name == 'main') {
@@ -167,13 +172,9 @@ class AndroidPlugin implements Plugin<Project> {
             testSourceSet = project.sourceSets.add("test${productFlavor.name.capitalize()}")
         }
 
-        def productFlavorDimension = new ProductFlavorDimension(productFlavor, mainSourceSet, testSourceSet)
+        def productFlavorDimension = new ProductFlavorDimension(productFlavor, mainSourceSet, testSourceSet, project.projectDir.absolutePath)
         productFlavors[productFlavor.name] = productFlavorDimension
 
-        productFlavor.conventionMapping.packageName = { extension.packageName }
-        productFlavor.conventionMapping.versionCode = { extension.versionCode }
-        productFlavor.conventionMapping.versionName = { extension.versionName }
-
         def assembleFlavour = project.tasks.add(productFlavorDimension.assembleTaskName)
         assembleFlavour.dependsOn {
             productFlavorDimension.variants.collect { "assemble${it.name}" }
@@ -187,33 +188,49 @@ class AndroidPlugin implements Plugin<Project> {
 
         assert productFlavorDimension.debugVariant != null
 
+        def flavorName = productFlavor.name.capitalize()
+
+        def configureTask = project.tasks.add("configure${flavorName}Test", ConfigureVariant)
+        configureTask.plugin = this
+        // TODO: hmm?
+        configureTask.productFlavor = productFlavors.get(productFlavor.name)
+        configureTask.buildType = buildTypes.get(BuildType.DEBUG)
+
         // Add a task to generate the test app manifest
-        def generateManifestTask = project.tasks.add("generate${productFlavor.name.capitalize()}TestManifest", GenerateManifest)
-        generateManifestTask.conventionMapping.outputFile = { project.file("$project.buildDir/manifests/test/$productFlavor.name/AndroidManifest.xml") }
-        generateManifestTask.conventionMapping.packageName = { productFlavor.packageName + '.test' }
-        generateManifestTask.conventionMapping.versionCode = { productFlavor.versionCode }
-        generateManifestTask.conventionMapping.versionName = { productFlavor.versionName }
+        def generateManifestTask = project.tasks.add("generate${flavorName}TestManifest", GenerateTestManifest)
+        generateManifestTask.dependsOn configureTask
+        generateManifestTask.conventionMapping.outputFile = { project.file("$project.buildDir/manifests/test/$flavorName/AndroidManifest.xml") }
+        generateManifestTask.conventionMapping.packageName = { getMainManifest().packageName + '.test' }
+
+        // Add a task to crunch resource files
+        def crunchTask = project.tasks.add("crunch${flavorName}TestResources", CrunchResources)
+        crunchTask.dependsOn configureTask
+        crunchTask.provider = this
+        crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/test/$flavorName") }
+
+        // Add a task to generate resource package
+        def processResources = project.tasks.add("process${flavorName}TestResources", ProcessResources)
+        processResources.dependsOn generateManifestTask, crunchTask
+        processResources.provider = this
+        processResources.conventionMapping.manifestFile = { generateManifestTask.outputFile }
+        processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
+        // TODO: unify with generateManifestTask somehow?
+        processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/test/$flavorName") }
+        processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/${project.archivesBaseName}-test${flavorName}.ap_") }
+        processResources.aaptOptions = extension.aaptOptions
 
         // Add a task to compile the test application
-        def testCompile = project.tasks.add("compile${productFlavor.name.capitalize()}Test", Compile)
+        def testCompile = project.tasks.add("compile${flavorName}Test", Compile)
+        testCompile.dependsOn processResources
         testCompile.source test.java, productFlavorDimension.testSource.java
         testCompile.classpath = test.compileClasspath + productFlavorDimension.debugVariant.runtimeClasspath
-        testCompile.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/test/$productFlavor.name") }
+        testCompile.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/test/$flavorName") }
         testCompile.options.bootClasspath = getRuntimeJar()
 
-        // Add a task to generate resource package
-        def processResources = project.tasks.add("process${productFlavor.name.capitalize()}TestResources", ProcessResources)
-        processResources.dependsOn generateManifestTask
-        processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/test/${project.archivesBaseName}-${productFlavor.name}.ap_") }
-        processResources.sdkDir = sdkDir
-        processResources.conventionMapping.sourceDirectories =  { [] }
-        processResources.conventionMapping.androidManifestFile = { generateManifestTask.outputFile }
-        processResources.conventionMapping.packageName = { generateManifestTask.packageName }
-        processResources.conventionMapping.includeFiles = { [getRuntimeJar()] }
-
         def testApp = new TestAppVariant(productFlavor)
         testApp.runtimeClasspath = testCompile.outputs.files + testCompile.classpath
         testApp.resourcePackage = project.files({processResources.packageFile}) { builtBy processResources }
+        testApp.compileTask = testCompile
         addPackageTasks(testApp)
 
         project.tasks.check.dependsOn "assemble${testApp.name}"
@@ -224,60 +241,61 @@ class AndroidPlugin implements Plugin<Project> {
         variants << variant
         buildType.variants << variant
         productFlavor.variants << variant
-        if (buildType.name == 'debug') {
+        if (buildType.name == BuildType.DEBUG) {
             productFlavor.debugVariant = variant
         }
 
         // add the base configure task
         def configureTask = project.tasks.add("configure${variant.name}", ConfigureVariant)
         configureTask.plugin = this
-        configureTask.mainProductFlavor = variant.productFlavor
-        configureTask.productFlavor = variant.productFlavor
-        configureTask.buildType = variant.buildType
+        configureTask.productFlavor = productFlavors.get(productFlavor.name)
+        configureTask.buildType = buildTypes.get(buildType.name)
 
-        // Add a task to generate the manifest
-        def generateManifestTask = project.tasks.add("generate${variant.name}Manifest", GenerateManifest)
-        generateManifestTask.dependsOn configureTask
-        generateManifestTask.sourceFile = project.file('src/main/AndroidManifest.xml')
-        generateManifestTask.conventionMapping.outputFile = { project.file("$project.buildDir/manifests/main/$variant.dirName/AndroidManifest.xml") }
-        generateManifestTask.conventionMapping.packageName = { getMainManifest().packageName }
-        generateManifestTask.conventionMapping.versionCode = { productFlavor.productFlavor.versionCode }
-        generateManifestTask.conventionMapping.versionName = { productFlavor.productFlavor.versionName }
+        // Add a task to merge the manifest(s)
+        def mergeManifestTask = project.tasks.add("merge${variant.name}Manifest", MergeManifest)
+        mergeManifestTask.dependsOn configureTask
+        mergeManifestTask.provider = this
+        mergeManifestTask.conventionMapping.mergedManifest = { project.file("$project.buildDir/manifests/$variant.dirName/AndroidManifest.xml") }
 
         // Add a task to crunch resource files
         def crunchTask = project.tasks.add("crunch${variant.name}Resources", CrunchResources)
         crunchTask.dependsOn configureTask
-        crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/main/$variant.dirName") }
-        crunchTask.sdkDir = sdkDir
-        crunchTask.conventionMapping.sourceDirectories =  {
-            (main.resources.srcDirs + productFlavor.mainSource.resources.srcDirs + buildType.mainSource.resources.srcDirs).findAll { it.exists() }
-        }
+        crunchTask.provider = this
+        crunchTask.conventionMapping.outputDir = { project.file("$project.buildDir/resources/$variant.dirName/res") }
+
+        // Add a task to create the BuildConfig class
+        def generateBuildConfigTask = project.tasks.add("generate${variant.name}BuildConfig", GenerateBuildConfigTask)
+        generateBuildConfigTask.dependsOn configureTask
+        generateBuildConfigTask.provider = this
+        generateBuildConfigTask.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/${variant.dirName}") }
 
         // Add a task to generate resource source files
         def processResources = project.tasks.add("process${variant.name}Resources", ProcessResources)
-        processResources.dependsOn generateManifestTask, crunchTask
-        processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/main/$variant.dirName") }
+        processResources.dependsOn mergeManifestTask, crunchTask
+        processResources.provider = this
+        processResources.conventionMapping.manifestFile = { mergeManifestTask.mergedManifest }
+        processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
+        // TODO: unify with generateBuilderConfig somehow?
+        processResources.conventionMapping.sourceOutputDir = { project.file("$project.buildDir/source/$variant.dirName") }
         processResources.conventionMapping.packageFile = { project.file("$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_") }
-        processResources.sdkDir = sdkDir
-        processResources.conventionMapping.sourceDirectories =  {
-            ([crunchTask.outputDir] + main.resources.srcDirs + productFlavor.mainSource.resources.srcDirs + buildType.mainSource.resources.srcDirs).findAll { it.exists() }
+        if (buildType.buildType.runProguard) {
+            processResources.conventionMapping.proguardFile = { project.file("$project.buildDir/proguard/${variant.dirName}/rules.txt") }
         }
-        processResources.conventionMapping.androidManifestFile = { generateManifestTask.outputFile }
-        processResources.conventionMapping.includeFiles = { [getRuntimeJar()] }
-        processResources.conventionMapping.packageName = { productFlavor.productFlavor.packageName }
+        processResources.aaptOptions = extension.aaptOptions
 
         // Add a compile task
         def compileTaskName = "compile${variant.name}"
         def compileTask = project.tasks.add(compileTaskName, Compile)
-        compileTask.dependsOn processResources
+        compileTask.dependsOn processResources, generateBuildConfigTask
         compileTask.source main.java, buildType.mainSource.java, productFlavor.mainSource.java, { processResources.sourceOutputDir }
         compileTask.classpath = main.compileClasspath
-        compileTask.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/main/$variant.dirName") }
+        compileTask.conventionMapping.destinationDir = { project.file("$project.buildDir/classes/$variant.dirName") }
         compileTask.options.bootClasspath = getRuntimeJar()
 
         // Wire up the outputs
         variant.runtimeClasspath = project.files(compileTask.outputs, main.compileClasspath)
         variant.resourcePackage = project.files({processResources.packageFile}) { builtBy processResources }
+        variant.compileTask = compileTask
 
         addPackageTasks(variant)
     }
@@ -286,15 +304,17 @@ class AndroidPlugin implements Plugin<Project> {
         // Add a dex task
         def dexTaskName = "dex${variant.name}"
         def dexTask = project.tasks.add(dexTaskName, Dex)
-        dexTask.sdkDir = sdkDir
+        dexTask.dependsOn variant.compileTask
+        dexTask.provider = this
         dexTask.conventionMapping.sourceFiles = { variant.runtimeClasspath }
         dexTask.conventionMapping.outputFile = { project.file("${project.buildDir}/libs/${project.archivesBaseName}-${variant.baseName}.dex") }
+        dexTask.dexOptions = extension.dexOptions
 
         // Add a task to generate application package
         def packageApp = project.tasks.add("package${variant.name}", PackageApplication)
         packageApp.dependsOn variant.resourcePackage, dexTask
+        packageApp.provider = this
         packageApp.conventionMapping.outputFile = { project.file("$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}-unaligned.apk") }
-        packageApp.sdkDir = sdkDir
         packageApp.conventionMapping.resourceFile = { variant.resourcePackage.singleFile }
         packageApp.conventionMapping.dexFile = { dexTask.outputFile }
 
@@ -323,4 +343,47 @@ class AndroidPlugin implements Plugin<Project> {
         installApp.conventionMapping.packageFile = { appTask.outputFile }
         installApp.sdkDir = sdkDir
     }
+
+    @Override
+    SdkParser getSdkParser() {
+        if (androidSdkParser == null) {
+            androidSdkParser = new DefaultSdkParser(sdkDir.absolutePath)
+        }
+
+        return androidSdkParser;
+    }
+
+    @Override
+    String getTarget() {
+        return extension.target;
+    }
+
+    @Override
+    ILogger getLogger() {
+        if (androidLogger == null) {
+            androidLogger = new AndroidLogger(project.logger)
+        }
+
+        return androidLogger
+    }
+
+    @Override
+    boolean isVerbose() {
+        return project.logger.isEnabled(LogLevel.DEBUG)
+    }
+
+    @Override
+    void setAndroidBuilder(AndroidBuilder androidBuilder) {
+        this.androidBuilder = androidBuilder
+    }
+
+    @Override
+    ProductFlavorHolder getMainFlavor() {
+        return productFlavors.get('main')
+    }
+
+    @Override
+    AndroidBuilder getAndroidBuilder() {
+        return androidBuilder
+    }
 }
diff --git a/gradle/src/main/groovy/com/android/build/gradle/BaseAndroidTask.groovy b/gradle/src/main/groovy/com/android/build/gradle/BaseAndroidTask.groovy
new file mode 100644 (file)
index 0000000..9f62a39
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.build.gradle
+
+import org.gradle.api.DefaultTask
+
+abstract class BaseAndroidTask extends DefaultTask {
+
+    AndroidBuilderProvider provider
+}
index 26a0259..1c913da 100644 (file)
 package com.android.build.gradle
 
 import com.android.builder.AndroidBuilder
-import com.android.builder.BuildType
-import com.android.builder.DefaultSdkParser
-import com.android.builder.ProductFlavor
-import com.android.utils.StdLogger
+import com.android.builder.BuildTypeHolder
+import com.android.builder.ProductFlavorHolder
 import org.gradle.api.DefaultTask
 import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
 import org.gradle.api.tasks.TaskAction
 
 /**
  */
 class ConfigureVariant extends DefaultTask {
-    AndroidPlugin plugin
+    AndroidBasePlugin plugin
 
     @Input
-    BuildType buildType
+    BuildTypeHolder buildType
 
-    @Input
-    ProductFlavor mainProductFlavor
-
-    @Input
-    ProductFlavor productFlavor
+    @Input @Optional
+    ProductFlavorHolder productFlavor
 
     @TaskAction
     void generate() {
-        plugin.androidBuilder = new AndroidBuilder(new DefaultSdkParser(sdkDir),
-                    new StdLogger(SdkLogger.Level.VERBOSE), true)
+        AndroidBuilder androidBuilder = new AndroidBuilder(plugin.sdkParser, plugin.logger, plugin.verbose)
+
+        androidBuilder.setTarget(plugin.target)
+
+        androidBuilder.setBuildVariant(plugin.mainFlavor, buildType)
+
+        if (productFlavor != null) {
+            androidBuilder.addProductFlavor(productFlavor)
+        }
 
-        plugin.androidBuilder.setTarget(plugin.extension.target)
-        plugin.androidBuilder.setBuildVariant(mainProductFlavor, productFlavor, buildType)
+        plugin.setAndroidBuilder(androidBuilder)
     }
 }
index b29a7e3..ca4a518 100644 (file)
  */
 package com.android.build.gradle
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.*
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
 
-class CrunchResources extends DefaultTask {
+class CrunchResources extends BaseAndroidTask {
     @OutputDirectory
     File outputDir
 
-    @Input
-    File sdkDir
-
-    @InputFiles
-    Iterable<File> sourceDirectories
-
     @TaskAction
     void generate() {
-        project.exec {
-            executable = new File(getSdkDir(), "platform-tools/aapt")
-            args 'crunch'
-            args '-C', getOutputDir()
-            getSourceDirectories().each {
-                args '-S', it
-            }
-        }
+        provider.androidBuilder.preprocessResources(getOutputDir().absolutePath)
     }
 }
index a9fa1ab..3ee28c5 100644 (file)
  */
 package com.android.build.gradle
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.*
+import com.android.builder.DexOptions
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
 
-class Dex extends DefaultTask {
+class Dex extends BaseAndroidTask {
     @OutputFile
     File outputFile
 
-    @Input
-    File sdkDir
-
     @InputFiles
     Iterable<File> sourceFiles
 
+    @Input
+    DexOptions dexOptions
+
     @TaskAction
     void generate() {
-        project.exec {
-            executable = new File(getSdkDir(), "platform-tools/dx")
-            args '--dex'
-            args '--output', getOutputFile()
-            getSourceFiles().each {
-                args it
+        List<String> files = new ArrayList<String>();
+        for (File f : getSourceFiles()) {
+            if (f != null && f.exists()) {
+                files.add(f.absolutePath)
             }
         }
+
+        provider.androidBuilder.convertBytecode(files, getOutputFile().absolutePath, dexOptions)
     }
 }
  */
 package com.android.build.gradle
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.InputFile
 import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.OutputFile
-import com.android.build.gradle.internal.AndroidManifest
 import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
 
-class GenerateManifest extends DefaultTask {
-    @InputFile @Optional
-    File sourceFile
-
-    @OutputFile
-    File outputFile
-
-    @Input
-    String packageName
+class GenerateBuildConfigTask extends BaseAndroidTask {
 
-    @Input
-    Integer versionCode
+    @OutputDirectory
+    File sourceOutputDir
 
-    @Input
-    String versionName
+    @Input @Optional
+    List<String> optionalJavaLines;
 
     @TaskAction
-    def generate() {
-        AndroidManifest manifest = new AndroidManifest()
-        if (getSourceFile() != null) {
-            manifest.load(getSourceFile())
-        }
-        manifest.packageName = getPackageName()
-        manifest.versionCode = getVersionCode()
-        manifest.versionName = getVersionName()
-        manifest.save(getOutputFile())
+    void generate() {
+        provider.androidBuilder.generateBuildConfig(getSourceOutputDir().absolutePath,
+                optionalJavaLines);
     }
 }
-
@@ -23,7 +23,7 @@ import org.gradle.api.tasks.OutputFile
 import com.android.build.gradle.internal.AndroidManifest
 import org.gradle.api.tasks.Optional
 
-class GenerateManifest extends DefaultTask {
+class GenerateTestManifest extends DefaultTask {
     @InputFile @Optional
     File sourceFile
 
@@ -33,12 +33,6 @@ class GenerateManifest extends DefaultTask {
     @Input
     String packageName
 
-    @Input
-    Integer versionCode
-
-    @Input
-    String versionName
-
     @TaskAction
     def generate() {
         AndroidManifest manifest = new AndroidManifest()
@@ -46,8 +40,6 @@ class GenerateManifest extends DefaultTask {
             manifest.load(getSourceFile())
         }
         manifest.packageName = getPackageName()
-        manifest.versionCode = getVersionCode()
-        manifest.versionName = getVersionName()
         manifest.save(getOutputFile())
     }
 }
diff --git a/gradle/src/main/groovy/com/android/build/gradle/MergeManifest.groovy b/gradle/src/main/groovy/com/android/build/gradle/MergeManifest.groovy
new file mode 100644 (file)
index 0000000..8c4fbc8
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.build.gradle
+
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+/**
+ */
+class MergeManifest extends BaseAndroidTask {
+
+    @OutputFile
+    File mergedManifest
+
+    @TaskAction
+    void generate() {
+        provider.androidBuilder.mergeManifest(getMergedManifest().absolutePath)
+    }
+}
index 99a8516..421ed5b 100644 (file)
  */
 package com.android.build.gradle
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.*
+import com.android.builder.packaging.DuplicateFileException
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
 
-class PackageApplication extends DefaultTask {
+class PackageApplication extends BaseAndroidTask {
     @OutputFile
     File outputFile
 
-    @Input
-    File sdkDir
-
     @InputFile
     File resourceFile
 
     @InputFile
     File dexFile
 
+    @InputDirectory @Optional
+    File jniDir
+
     @TaskAction
     void generate() {
-        def antJar = new File(getSdkDir(), "tools/lib/anttasks.jar")
-        ant.taskdef(resource: "anttasks.properties", classpath: antJar)
-        ant.apkbuilder(apkFilepath: getOutputFile(),
-                resourcefile: project.fileResolver.withBaseDir(getOutputFile().parentFile).resolveAsRelativePath(getResourceFile()),
-                outfolder: getOutputFile().getParentFile(),
-                debugsigning: true) {
-            dex(path: getDexFile())
+
+        try {
+            provider.androidBuilder.packageApk(
+                    getResourceFile().absolutePath,
+                    getDexFile().absolutePath,
+                    getJniDir()?.absolutePath,
+                    getOutputFile().absolutePath)
+        } catch (DuplicateFileException e) {
+            def logger = getLogger()
+            logger.error("Error: duplicate files during packaging of APK " + getOutputFile().absolutePath)
+            logger.error("\tPath in archive: " + e.archivePath)
+            logger.error("\tOrigin 1: " + e.file1)
+            logger.error("\tOrigin 2: " + e.file2)
+            throw new RuntimeException();
         }
     }
 }
index 92af061..547a6b6 100644 (file)
  */
 package com.android.build.gradle
 
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.OutputDirectory
+import com.android.builder.AaptOptions
 import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.InputDirectory
 import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.InputFile
 
-class ProcessResources extends DefaultTask {
-    @Input
-    File sdkDir
-
-    @InputFiles
-    Iterable<File> sourceDirectories
-
-    @InputFiles
-    Iterable<File> includeFiles
+class ProcessResources extends BaseAndroidTask {
 
     @InputFile
-    File androidManifestFile
+    File manifestFile
 
-    @Input
-    String packageName
+    @InputDirectory @Optional
+    File crunchDir
 
     @OutputDirectory @Optional
     File sourceOutputDir
 
-    @OutputFile
+    @OutputFile @Optional
     File packageFile
 
+    @OutputFile @Optional
+    File proguardFile
+
+    @Input
+    AaptOptions aaptOptions
+
     @TaskAction
     void generate() {
-        project.exec {
-            executable = new File(getSdkDir(), "platform-tools/aapt")
-            args 'package'
-            args '-f'
-            args '-m'
-            args '--generate-dependencies'
-            args '--rename-manifest-package', getPackageName()
-            if (getSourceOutputDir() != null) {
-                args '-J', getSourceOutputDir()
-            }
-            args '-F', getPackageFile()
-            args '-M', getAndroidManifestFile()
-            getSourceDirectories().each {
-                args '-S', it
-            }
-            getIncludeFiles().each {
-                args '-I', it
-            }
-        }
+
+        provider.androidBuilder.processResources(
+                getManifestFile().absolutePath,
+                getCrunchDir()?.absolutePath,
+                getSourceOutputDir()?.absolutePath,
+                getPackageFile()?.absolutePath,
+                getProguardFile()?.absolutePath,
+                getAaptOptions())
     }
 }
index d448a57..11374bf 100644 (file)
@@ -16,6 +16,7 @@
 package com.android.build.gradle.internal
 
 import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.compile.Compile
 
 /**
  * Represents something that can be packaged into an APK and installed.
@@ -34,4 +35,6 @@ public interface ApplicationVariant {
     FileCollection getRuntimeClasspath()
 
     FileCollection getResourcePackage()
+
+    Compile getCompileTask()
 }
diff --git a/gradle/src/main/groovy/com/android/build/gradle/internal/BaseDimension.groovy b/gradle/src/main/groovy/com/android/build/gradle/internal/BaseDimension.groovy
new file mode 100644 (file)
index 0000000..41afe34
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.build.gradle.internal
+
+import org.gradle.api.tasks.SourceSet
+import com.android.builder.PathProvider
+
+/**
+ * Base Dimension providing an implementation of PathProvider for a given SourceSet.
+ */
+class BaseDimension implements PathProvider {
+
+    final SourceSet mainSource
+    private final String baseDir
+    private final String name
+
+
+    BaseDimension(SourceSet mainSource, String baseDir, String name) {
+        this.mainSource = mainSource
+        this.baseDir = baseDir
+        this.name = name
+    }
+
+    @Override
+    Set<File> getJavaSource() {
+        return mainSource.allJava.srcDirs
+    }
+
+    @Override
+    Set<File> getJavaResources() {
+        return mainSource.resources.srcDirs
+    }
+
+    @Override
+    File getAndroidResources() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/res")
+    }
+
+    @Override
+    File getAndroidAssets() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/assets")
+    }
+
+    @Override
+    File getAndroidManifest() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/AndroidManifest.xml")
+    }
+
+    @Override
+    File getAidlSource() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/aidl")
+    }
+
+    @Override
+    File getRenderscriptSource() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/rs")
+    }
+
+    @Override
+    File getNativeSource() {
+        // FIXME: make this configurable by the SourceSet
+        return new File("$baseDir/src/$name/jni")
+    }
+}
index 95efad5..450da6d 100644 (file)
@@ -17,15 +17,15 @@ package com.android.build.gradle.internal
 
 import com.android.builder.BuildType
 import org.gradle.api.tasks.SourceSet
+import com.android.builder.BuildTypeHolder
 
-class BuildTypeDimension {
+class BuildTypeDimension extends BaseDimension implements BuildTypeHolder {
     final BuildType buildType
     final Set<ProductionAppVariant> variants = []
-    final SourceSet mainSource
 
-    BuildTypeDimension(BuildType buildType, SourceSet mainSource) {
+    BuildTypeDimension(BuildType buildType, SourceSet mainSource, String baseDir) {
+        super(mainSource, baseDir, buildType.name)
         this.buildType = buildType
-        this.mainSource = mainSource
     }
 
     String getName() {
index 8d43c35..6e9b082 100644 (file)
@@ -17,17 +17,18 @@ package com.android.build.gradle.internal
 
 import com.android.builder.ProductFlavor
 import org.gradle.api.tasks.SourceSet
+import com.android.builder.ProductFlavorHolder
 
-class ProductFlavorDimension {
+class ProductFlavorDimension extends BaseDimension implements ProductFlavorHolder {
     final ProductFlavor productFlavor
     final Set<ProductionAppVariant> variants = []
-    final SourceSet mainSource
     final SourceSet testSource
     ProductionAppVariant debugVariant
 
-    ProductFlavorDimension(ProductFlavor productFlavor, SourceSet mainSource, SourceSet testSource) {
+    ProductFlavorDimension(ProductFlavor productFlavor, SourceSet mainSource, SourceSet testSource,
+                           String baseDir) {
+        super(mainSource, baseDir, productFlavor.name)
         this.productFlavor = productFlavor
-        this.mainSource = mainSource
         this.testSource = testSource
     }
 
index 15a22a2..f427f90 100644 (file)
@@ -18,6 +18,7 @@ package com.android.build.gradle.internal
 import com.android.builder.BuildType
 import com.android.builder.ProductFlavor
 import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.compile.Compile
 
 class ProductionAppVariant implements ApplicationVariant {
     final String name
@@ -25,6 +26,7 @@ class ProductionAppVariant implements ApplicationVariant {
     final ProductFlavor productFlavor
     FileCollection runtimeClasspath
     FileCollection resourcePackage
+    Compile compileTask
 
     ProductionAppVariant(BuildType buildType, ProductFlavor productFlavor) {
         this.name = "${productFlavor.name.capitalize()}${buildType.name.capitalize()}"
index f15806d..3bbde1e 100644 (file)
@@ -17,12 +17,14 @@ package com.android.build.gradle.internal
 
 import com.android.builder.ProductFlavor
 import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.compile.Compile
 
 class TestAppVariant implements ApplicationVariant {
     final String name
     final ProductFlavor productFlavor
     FileCollection runtimeClasspath
     FileCollection resourcePackage
+    Compile compileTask
 
     TestAppVariant(ProductFlavor productFlavor) {
         this.name = "${productFlavor.name.capitalize()}Test"